diff options
55 files changed, 1899 insertions, 1477 deletions
diff --git a/Android.bp b/Android.bp index 2af7ca4186c0..ac1cd47f2194 100644 --- a/Android.bp +++ b/Android.bp @@ -366,7 +366,9 @@ java_defaults { sdk_version: "core_platform", libs: [ + "app-compat-annotations", "ext", + "unsupportedappusage", "updatable_media_stubs", ], @@ -426,7 +428,6 @@ java_library { name: "framework-minus-apex", defaults: ["framework-defaults"], srcs: [":framework-non-updatable-sources"], - libs: ["app-compat-annotations"], installable: true, javac_shard_size: 150, required: [ @@ -468,14 +469,16 @@ java_library { defaults: ["framework-defaults"], srcs: [":framework-all-sources"], installable: false, - libs: ["app-compat-annotations"], } java_library { name: "framework-annotation-proc", defaults: ["framework-defaults"], srcs: [":framework-all-sources"], - libs: ["app-compat-annotations"], + libs: [ + "app-compat-annotations", + "unsupportedappusage", + ], installable: false, plugins: [ "unsupportedappusage-annotation-processor", diff --git a/apex/sdkext/derive_sdk/derive_sdk.cpp b/apex/sdkext/derive_sdk/derive_sdk.cpp index 0aacebefaaca..7536def60767 100644 --- a/apex/sdkext/derive_sdk/derive_sdk.cpp +++ b/apex/sdkext/derive_sdk/derive_sdk.cpp @@ -68,7 +68,7 @@ int main(int, char**) { auto itr = std::min_element(versions.begin(), versions.end()); std::string prop_value = itr == versions.end() ? "0" : std::to_string(*itr); - if (!android::base::SetProperty("persist.com.android.sdkext.sdk_info", prop_value)) { + if (!android::base::SetProperty("ro.build.version.extensions.r", prop_value)) { LOG(ERROR) << "failed to set sdk_info prop"; return EXIT_FAILURE; } diff --git a/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java b/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java index 331ef21935bb..d3b9397d45f2 100644 --- a/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java +++ b/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java @@ -38,7 +38,7 @@ public class SdkExtensions { private static final int R_EXTENSION_INT; static { - R_EXTENSION_INT = SystemProperties.getInt("persist.com.android.sdkext.sdk_info", 0); + R_EXTENSION_INT = SystemProperties.getInt("ro.build.version.extensions.r", 0); } /** diff --git a/api/current.txt b/api/current.txt index 23b5bc6224f1..d2d63af3a544 100644 --- a/api/current.txt +++ b/api/current.txt @@ -28903,6 +28903,7 @@ package android.net { public class NetworkRequest implements android.os.Parcelable { method public int describeContents(); + method @Nullable public android.net.NetworkSpecifier getNetworkSpecifier(); method public boolean hasCapability(int); method public boolean hasTransport(int); method public void writeToParcel(android.os.Parcel, int); @@ -45499,6 +45500,7 @@ package android.telephony.data { field public static final int TYPE_MCX = 1024; // 0x400 field public static final int TYPE_MMS = 2; // 0x2 field public static final int TYPE_SUPL = 4; // 0x4 + field public static final int TYPE_XCAP = 2048; // 0x800 } public static class ApnSetting.Builder { @@ -46808,7 +46810,7 @@ package android.text.format { field public static final long WEEK_IN_MILLIS = 604800000L; // 0x240c8400L field public static final String YEAR_FORMAT = "%Y"; field public static final String YEAR_FORMAT_TWO_DIGITS = "%g"; - field public static final long YEAR_IN_MILLIS = 31449600000L; // 0x7528ad000L + field @Deprecated public static final long YEAR_IN_MILLIS = 31449600000L; // 0x7528ad000L field @Deprecated public static final int[] sameMonthTable; field @Deprecated public static final int[] sameYearTable; } @@ -48245,6 +48247,13 @@ package android.util { ctor public Base64OutputStream(java.io.OutputStream, int); } + public final class CloseGuard { + ctor public CloseGuard(); + method public void close(); + method public void open(@NonNull String); + method public void warnIfOpen(); + } + @Deprecated public final class Config { field @Deprecated public static final boolean DEBUG = false; field @Deprecated public static final boolean LOGD = true; diff --git a/api/system-current.txt b/api/system-current.txt index 0639e635ca5f..bf0bfb6b5fc8 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -1343,15 +1343,24 @@ package android.bluetooth { public final class BluetoothDevice implements android.os.Parcelable { method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean cancelBondProcess(); + method public boolean cancelPairing(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getBatteryLevel(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getMessageAccessPermission(); method @Nullable @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public byte[] getMetadata(int); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getPhonebookAccessPermission(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getSimAccessPermission(); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isBondingInitiatedLocally(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isConnected(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean isEncrypted(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean isInSilenceMode(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean removeBond(); method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public boolean setAlias(@NonNull String); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMessageAccessPermission(int); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setMetadata(int, @NonNull byte[]); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setPhonebookAccessPermission(int); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setPin(@Nullable String); method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setSilenceMode(boolean); + method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setSimAccessPermission(int); field public static final int ACCESS_ALLOWED = 1; // 0x1 field public static final int ACCESS_REJECTED = 2; // 0x2 field public static final int ACCESS_UNKNOWN = 0; // 0x0 diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt index c64e3d884a70..432a5fd8efec 100644 --- a/api/system-lint-baseline.txt +++ b/api/system-lint-baseline.txt @@ -144,6 +144,16 @@ ProtectedMember: android.service.contentcapture.ContentCaptureService#dump(java. ProtectedMember: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context): +PublicTypedef: android.content.integrity.AtomicFormula.Key: Don't expose @IntDef: @Key must be hidden. + +PublicTypedef: android.content.integrity.AtomicFormula.Operator: Don't expose @IntDef: @Operator must be hidden. + +PublicTypedef: android.content.integrity.CompoundFormula.Connector: Don't expose @IntDef: @Connector must be hidden. + +PublicTypedef: android.content.integrity.Formula.Tag: Don't expose @IntDef: @Tag must be hidden. + +PublicTypedef: android.content.integrity.Rule.Effect: Don't expose @IntDef: @Effect must be hidden. + SamShouldBeLast: android.accounts.AccountManager#addAccount(String, String, String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler): diff --git a/api/test-lint-baseline.txt b/api/test-lint-baseline.txt index bf21ce71c9a4..3af392c05c8f 100644 --- a/api/test-lint-baseline.txt +++ b/api/test-lint-baseline.txt @@ -2444,6 +2444,12 @@ ProtectedMember: android.view.View#resetResolvedDrawables(): ProtectedMember: android.view.ViewGroup#resetResolvedDrawables(): +PublicTypedef: android.os.HwParcel.Status: Don't expose @IntDef: @Status must be hidden. + +PublicTypedef: android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability: Don't expose @IntDef: @MmTelCapability must be hidden. + +PublicTypedef: android.telephony.ims.feature.MmTelFeature.ProcessCallResult: Don't expose @IntDef: @ProcessCallResult must be hidden. + RawAidl: android.telephony.mbms.vendor.MbmsDownloadServiceBase: diff --git a/cmds/uiautomator/library/Android.bp b/cmds/uiautomator/library/Android.bp index 1173d57e5e3f..3a260639de0f 100644 --- a/cmds/uiautomator/library/Android.bp +++ b/cmds/uiautomator/library/Android.bp @@ -22,6 +22,7 @@ droiddoc { "android.test.runner", "junit", "android.test.base", + "unsupportedappusage", ], custom_template: "droiddoc-templates-sdk", installable: false, diff --git a/core/java/android/app/timedetector/ManualTimeSuggestion.java b/core/java/android/app/timedetector/ManualTimeSuggestion.java index 471606da4d75..55f92be14cd0 100644 --- a/core/java/android/app/timedetector/ManualTimeSuggestion.java +++ b/core/java/android/app/timedetector/ManualTimeSuggestion.java @@ -56,6 +56,7 @@ public final class ManualTimeSuggestion implements Parcelable { public ManualTimeSuggestion(@NonNull TimestampedValue<Long> utcTime) { mUtcTime = Objects.requireNonNull(utcTime); + Objects.requireNonNull(utcTime.getValue()); } private static ManualTimeSuggestion createFromParcel(Parcel in) { diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.java b/core/java/android/app/timedetector/PhoneTimeSuggestion.java index dd02af7a3ac7..4a89a1245473 100644 --- a/core/java/android/app/timedetector/PhoneTimeSuggestion.java +++ b/core/java/android/app/timedetector/PhoneTimeSuggestion.java @@ -166,7 +166,12 @@ public final class PhoneTimeSuggestion implements Parcelable { } /** Returns the builder for call chaining. */ - public Builder setUtcTime(TimestampedValue<Long> utcTime) { + public Builder setUtcTime(@Nullable TimestampedValue<Long> utcTime) { + if (utcTime != null) { + // utcTime can be null, but the value it holds cannot. + Objects.requireNonNull(utcTime.getValue()); + } + mUtcTime = utcTime; return this; } diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 49187dcde342..323c7d172033 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -17,6 +17,7 @@ package android.bluetooth; import android.Manifest; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; @@ -33,8 +34,12 @@ import android.os.Process; import android.os.RemoteException; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; + import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.UUID; /** @@ -771,6 +776,13 @@ public final class BluetoothDevice implements Parcelable { @UnsupportedAppUsage public static final String EXTRA_SDP_SEARCH_STATUS = "android.bluetooth.device.extra.SDP_SEARCH_STATUS"; + + /** @hide */ + @IntDef(prefix = "ACCESS_", value = {ACCESS_UNKNOWN, + ACCESS_ALLOWED, ACCESS_REJECTED}) + @Retention(RetentionPolicy.SOURCE) + public @interface AccessPermission{} + /** * For {@link #getPhonebookAccessPermission}, {@link #setPhonebookAccessPermission}, * {@link #getMessageAccessPermission} and {@link #setMessageAccessPermission}. @@ -1096,15 +1108,14 @@ public final class BluetoothDevice implements Parcelable { /** * Get the most recent identified battery level of this Bluetooth device - * <p>Requires {@link android.Manifest.permission#BLUETOOTH} * * @return Battery level in percents from 0 to 100, or {@link #BATTERY_LEVEL_UNKNOWN} if * Bluetooth is disabled, or device is disconnected, or does not have any battery reporting * service, or return value is invalid * @hide */ + @SystemApi @RequiresPermission(Manifest.permission.BLUETOOTH) - @UnsupportedAppUsage public int getBatteryLevel() { final IBluetooth service = sService; if (service == null) { @@ -1187,8 +1198,15 @@ public final class BluetoothDevice implements Parcelable { return false; } - /** @hide */ - @UnsupportedAppUsage + /** + * Gets whether bonding was initiated locally + * + * @return true if bonding is initiated locally, false otherwise + * + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH) public boolean isBondingInitiatedLocally() { final IBluetooth service = sService; if (service == null) { @@ -1480,15 +1498,20 @@ public final class BluetoothDevice implements Parcelable { return false; } - /** @hide */ - @UnsupportedAppUsage - public boolean setPasskey(int passkey) { - //TODO(BT) - /* - try { - return sService.setPasskey(this, true, 4, passkey); - } catch (RemoteException e) {Log.e(TAG, "", e);}*/ - return false; + /** + * Set the pin during pairing when the pairing method is {@link #PAIRING_VARIANT_PIN} + * + * @return true pin has been set false for error + * @hide + */ + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN) + public boolean setPin(@Nullable String pin) { + byte[] pinBytes = convertPinToBytes(pin); + if (pinBytes == null) { + return false; + } + return setPin(pinBytes); } /** @@ -1511,22 +1534,18 @@ public final class BluetoothDevice implements Parcelable { return false; } - /** @hide */ - public boolean setRemoteOutOfBandData() { - // TODO(BT) - /* - try { - return sService.setRemoteOutOfBandData(this); - } catch (RemoteException e) {Log.e(TAG, "", e);}*/ - return false; - } - - /** @hide */ - @UnsupportedAppUsage - public boolean cancelPairingUserInput() { + /** + * Cancels pairing to this device + * + * @return true if pairing cancelled successfully, false otherwise + * + * @hide + */ + @SystemApi + public boolean cancelPairing() { final IBluetooth service = sService; if (service == null) { - Log.e(TAG, "BT not enabled. Cannot create pairing user input"); + Log.e(TAG, "BT not enabled. Cannot cancel pairing"); return false; } try { @@ -1537,17 +1556,6 @@ public final class BluetoothDevice implements Parcelable { return false; } - /** @hide */ - @UnsupportedAppUsage - public boolean isBluetoothDock() { - // TODO(BT) - /* - try { - return sService.isBluetoothDock(this); - } catch (RemoteException e) {Log.e(TAG, "", e);}*/ - return false; - } - boolean isBluetoothEnabled() { boolean ret = false; BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); @@ -1558,13 +1566,14 @@ public final class BluetoothDevice implements Parcelable { } /** - * Requires {@link android.Manifest.permission#BLUETOOTH}. + * Gets whether the phonebook access is allowed for this bluetooth device * * @return Whether the phonebook access is allowed to this device. Can be {@link * #ACCESS_UNKNOWN}, {@link #ACCESS_ALLOWED} or {@link #ACCESS_REJECTED}. * @hide */ - @UnsupportedAppUsage + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH) public int getPhonebookAccessPermission() { final IBluetooth service = sService; if (service == null) { @@ -1667,14 +1676,14 @@ public final class BluetoothDevice implements Parcelable { } /** - * Requires {@link android.Manifest.permission#BLUETOOTH}. + * Gets whether message access is allowed to this bluetooth device * - * @return Whether the message access is allowed to this device. Can be {@link #ACCESS_UNKNOWN}, - * {@link #ACCESS_ALLOWED} or {@link #ACCESS_REJECTED}. + * @return Whether the message access is allowed to this device. * @hide */ - @UnsupportedAppUsage - public int getMessageAccessPermission() { + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH) + public @AccessPermission int getMessageAccessPermission() { final IBluetooth service = sService; if (service == null) { return ACCESS_UNKNOWN; @@ -1689,15 +1698,18 @@ public final class BluetoothDevice implements Parcelable { /** * Sets whether the message access is allowed to this device. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}. * - * @param value Can be {@link #ACCESS_UNKNOWN}, {@link #ACCESS_ALLOWED} or {@link - * #ACCESS_REJECTED}. + * @param value is the value we are setting the message access permission to * @return Whether the value has been successfully set. * @hide */ - @UnsupportedAppUsage - public boolean setMessageAccessPermission(int value) { + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) + public boolean setMessageAccessPermission(@AccessPermission int value) { + // Validates param value is one of the accepted constants + if (value != ACCESS_ALLOWED && value != ACCESS_REJECTED && value != ACCESS_UNKNOWN) { + throw new IllegalArgumentException(value + "is not a valid AccessPermission value"); + } final IBluetooth service = sService; if (service == null) { return false; @@ -1711,13 +1723,14 @@ public final class BluetoothDevice implements Parcelable { } /** - * Requires {@link android.Manifest.permission#BLUETOOTH}. + * Gets whether sim access is allowed for this bluetooth device * - * @return Whether the Sim access is allowed to this device. Can be {@link #ACCESS_UNKNOWN}, - * {@link #ACCESS_ALLOWED} or {@link #ACCESS_REJECTED}. + * @return Whether the Sim access is allowed to this device. * @hide */ - public int getSimAccessPermission() { + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH) + public @AccessPermission int getSimAccessPermission() { final IBluetooth service = sService; if (service == null) { return ACCESS_UNKNOWN; @@ -1732,14 +1745,14 @@ public final class BluetoothDevice implements Parcelable { /** * Sets whether the Sim access is allowed to this device. - * <p>Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}. * * @param value Can be {@link #ACCESS_UNKNOWN}, {@link #ACCESS_ALLOWED} or {@link * #ACCESS_REJECTED}. * @return Whether the value has been successfully set. * @hide */ - @UnsupportedAppUsage + @SystemApi + @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED) public boolean setSimAccessPermission(int value) { final IBluetooth service = sService; if (service == null) { @@ -1970,7 +1983,7 @@ public final class BluetoothDevice implements Parcelable { * @return the pin code as a UTF-8 byte array, or null if it is an invalid Bluetooth pin. * @hide */ - @UnsupportedAppUsage + @VisibleForTesting public static byte[] convertPinToBytes(String pin) { if (pin == null) { return null; diff --git a/core/java/android/content/pm/ParceledListSlice.aidl b/core/java/android/content/pm/ParceledListSlice.aidl index c02cc6a5472e..5031fbaca9e6 100644 --- a/core/java/android/content/pm/ParceledListSlice.aidl +++ b/core/java/android/content/pm/ParceledListSlice.aidl @@ -16,4 +16,4 @@ package android.content.pm; -parcelable ParceledListSlice; +parcelable ParceledListSlice<T>; diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java index 4270740cc722..471b23e04775 100644 --- a/core/java/android/net/NetworkRequest.java +++ b/core/java/android/net/NetworkRequest.java @@ -17,6 +17,7 @@ package android.net; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.UnsupportedAppUsage; @@ -461,6 +462,14 @@ public class NetworkRequest implements Parcelable { return networkCapabilities.hasTransport(transportType); } + /** + * @see Builder#setNetworkSpecifier(NetworkSpecifier) + */ + @Nullable + public NetworkSpecifier getNetworkSpecifier() { + return networkCapabilities.getNetworkSpecifier(); + } + public String toString() { return "NetworkRequest [ " + type + " id=" + requestId + (legacyType != ConnectivityManager.TYPE_NONE ? ", legacyType=" + legacyType : "") + diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 450bfaef50d4..1130f1db6256 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -519,11 +519,12 @@ public class Process { * @param appDataDir null-ok the data directory of the app. * @param invokeWith null-ok the command to invoke with. * @param packageName null-ok the name of the package this process belongs to. - * + * @param disabledCompatChanges null-ok list of disabled compat changes for the process being + * started. * @param zygoteArgs Additional arguments to supply to the zygote process. * @return An object that describes the result of the attempt to start the process. * @throws RuntimeException on fatal start failure - * + * * {@hide} */ public static ProcessStartResult start(@NonNull final String processClass, @@ -538,11 +539,12 @@ public class Process { @Nullable String appDataDir, @Nullable String invokeWith, @Nullable String packageName, + @Nullable long[] disabledCompatChanges, @Nullable String[] zygoteArgs) { return ZYGOTE_PROCESS.start(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, invokeWith, packageName, - /*useUsapPool=*/ true, zygoteArgs); + /*useUsapPool=*/ true, disabledCompatChanges, zygoteArgs); } /** @hide */ @@ -558,11 +560,12 @@ public class Process { @Nullable String appDataDir, @Nullable String invokeWith, @Nullable String packageName, + @Nullable long[] disabledCompatChanges, @Nullable String[] zygoteArgs) { return WebViewZygote.getProcess().start(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, invokeWith, packageName, - /*useUsapPool=*/ false, zygoteArgs); + /*useUsapPool=*/ false, disabledCompatChanges, zygoteArgs); } /** diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java index 3a55aff14659..c2d3eccfa3b1 100644 --- a/core/java/android/os/ZygoteProcess.java +++ b/core/java/android/os/ZygoteProcess.java @@ -306,6 +306,8 @@ public class ZygoteProcess { * @param appDataDir null-ok the data directory of the app. * @param invokeWith null-ok the command to invoke with. * @param packageName null-ok the name of the package this process belongs to. + * @param disabledCompatChanges null-ok list of disabled compat changes for the process being + * started. * @param zygoteArgs Additional arguments to supply to the zygote process. * * @return An object that describes the result of the attempt to start the process. @@ -323,6 +325,7 @@ public class ZygoteProcess { @Nullable String invokeWith, @Nullable String packageName, boolean useUsapPool, + @Nullable long[] disabledCompatChanges, @Nullable String[] zygoteArgs) { // TODO (chriswailes): Is there a better place to check this value? if (fetchUsapPoolEnabledPropWithMinInterval()) { @@ -333,7 +336,7 @@ public class ZygoteProcess { return startViaZygote(processClass, niceName, uid, gid, gids, runtimeFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, invokeWith, /*startChildZygote=*/ false, - packageName, useUsapPool, zygoteArgs); + packageName, useUsapPool, disabledCompatChanges, zygoteArgs); } catch (ZygoteStartFailedEx ex) { Log.e(LOG_TAG, "Starting VM process through Zygote failed"); @@ -534,6 +537,7 @@ public class ZygoteProcess { * @param startChildZygote Start a sub-zygote. This creates a new zygote process * that has its state cloned from this zygote process. * @param packageName null-ok the name of the package this process belongs to. + * @param disabledCompatChanges a list of disabled compat changes for the process being started. * @param extraArgs Additional arguments to supply to the zygote process. * @return An object that describes the result of the attempt to start the process. * @throws ZygoteStartFailedEx if process start failed for any reason @@ -552,6 +556,7 @@ public class ZygoteProcess { boolean startChildZygote, @Nullable String packageName, boolean useUsapPool, + @Nullable long[] disabledCompatChanges, @Nullable String[] extraArgs) throws ZygoteStartFailedEx { ArrayList<String> argsForZygote = new ArrayList<>(); @@ -623,6 +628,21 @@ public class ZygoteProcess { argsForZygote.add("--package-name=" + packageName); } + if (disabledCompatChanges != null && disabledCompatChanges.length > 0) { + final StringBuilder sb = new StringBuilder(); + sb.append("--disabled-compat-changes="); + + final int sz = disabledCompatChanges.length; + for (int i = 0; i < sz; i++) { + if (i != 0) { + sb.append(','); + } + sb.append(disabledCompatChanges[i]); + } + + argsForZygote.add(sb.toString()); + } + argsForZygote.add(processClass); if (extraArgs != null) { @@ -1170,7 +1190,8 @@ public class ZygoteProcess { gids, runtimeFlags, 0 /* mountExternal */, 0 /* targetSdkVersion */, seInfo, abi, instructionSet, null /* appDataDir */, null /* invokeWith */, true /* startChildZygote */, null /* packageName */, - false /* useUsapPool */, extraArgs); + false /* useUsapPool */, + null /* disabledCompatChanges */, extraArgs); } catch (ZygoteStartFailedEx ex) { throw new RuntimeException("Starting child-zygote through Zygote failed", ex); } diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java index e94b8006d24a..906251837ade 100644 --- a/core/java/android/text/format/DateUtils.java +++ b/core/java/android/text/format/DateUtils.java @@ -56,8 +56,12 @@ public class DateUtils public static final long DAY_IN_MILLIS = HOUR_IN_MILLIS * 24; public static final long WEEK_IN_MILLIS = DAY_IN_MILLIS * 7; /** - * This constant is actually the length of 364 days, not of a year! + * @deprecated Not all years have the same number of days, and this constant is actually the + * length of 364 days. Please use other date/time constructs such as + * {@link java.util.concurrent.TimeUnit}, {@link java.util.Calendar} or + * {@link java.time.Duration} instead. */ + @Deprecated public static final long YEAR_IN_MILLIS = WEEK_IN_MILLIS * 52; // The following FORMAT_* symbols are used for specifying the format of diff --git a/core/java/android/util/CloseGuard.java b/core/java/android/util/CloseGuard.java new file mode 100644 index 000000000000..c39a6c9aac93 --- /dev/null +++ b/core/java/android/util/CloseGuard.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +import android.annotation.NonNull; + +/** + * CloseGuard is a mechanism for flagging implicit finalizer cleanup of + * resources that should have been cleaned up by explicit close + * methods (aka "explicit termination methods" in Effective Java). + * <p> + * A simple example: <pre> {@code + * class Foo { + * + * private final CloseGuard guard = CloseGuard.get(); + * + * ... + * + * public Foo() { + * ...; + * guard.open("cleanup"); + * } + * + * public void cleanup() { + * guard.close(); + * ...; + * } + * + * protected void finalize() throws Throwable { + * try { + * // Note that guard could be null if the constructor threw. + * if (guard != null) { + * guard.warnIfOpen(); + * } + * cleanup(); + * } finally { + * super.finalize(); + * } + * } + * } + * }</pre> + * + * In usage where the resource to be explicitly cleaned up is + * allocated after object construction, CloseGuard protection can + * be deferred. For example: <pre> {@code + * class Bar { + * + * private final CloseGuard guard = CloseGuard.get(); + * + * ... + * + * public Bar() { + * ...; + * } + * + * public void connect() { + * ...; + * guard.open("cleanup"); + * } + * + * public void cleanup() { + * guard.close(); + * ...; + * Reference.reachabilityFence(this); + * // For full correctness in the absence of a close() call, other methods may also need + * // reachabilityFence() calls. + * } + * + * protected void finalize() throws Throwable { + * try { + * // Note that guard could be null if the constructor threw. + * if (guard != null) { + * guard.warnIfOpen(); + * } + * cleanup(); + * } finally { + * super.finalize(); + * } + * } + * } + * }</pre> + * + * When used in a constructor, calls to {@code open} should occur at + * the end of the constructor since an exception that would cause + * abrupt termination of the constructor will mean that the user will + * not have a reference to the object to cleanup explicitly. When used + * in a method, the call to {@code open} should occur just after + * resource acquisition. + */ +public final class CloseGuard { + private final dalvik.system.CloseGuard mImpl; + + /** + * Constructs a new CloseGuard instance. + * {@link #open(String)} can be used to set up the instance to warn on failure to close. + */ + public CloseGuard() { + mImpl = dalvik.system.CloseGuard.get(); + } + + /** + * Initializes the instance with a warning that the caller should have explicitly called the + * {@code closeMethodName} method instead of relying on finalization. + * + * @param closeMethodName non-null name of explicit termination method. Printed by warnIfOpen. + * @throws NullPointerException if closeMethodName is null. + */ + public void open(@NonNull String closeMethodName) { + mImpl.open(closeMethodName); + } + + /** Marks this CloseGuard instance as closed to avoid warnings on finalization. */ + public void close() { + mImpl.close(); + } + + /** + * Logs a warning if the caller did not properly cleanup by calling an explicit close method + * before finalization. + */ + public void warnIfOpen() { + mImpl.warnIfOpen(); + } +} diff --git a/core/java/com/android/internal/os/RuntimeInit.java b/core/java/com/android/internal/os/RuntimeInit.java index a21187165c65..fa823c4bf2f6 100644 --- a/core/java/com/android/internal/os/RuntimeInit.java +++ b/core/java/com/android/internal/os/RuntimeInit.java @@ -367,8 +367,8 @@ public class RuntimeInit { if (DEBUG) Slog.d(TAG, "Leaving RuntimeInit!"); } - protected static Runnable applicationInit(int targetSdkVersion, String[] argv, - ClassLoader classLoader) { + protected static Runnable applicationInit(int targetSdkVersion, long[] disabledCompatChanges, + String[] argv, ClassLoader classLoader) { // If the application calls System.exit(), terminate the process // immediately without running any shutdown hooks. It is not possible to // shutdown an Android application gracefully. Among other things, the @@ -377,6 +377,7 @@ public class RuntimeInit { nativeSetExitWithoutCleanup(true); VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion); + VMRuntime.getRuntime().setDisabledCompatChanges(disabledCompatChanges); final Arguments args = new Arguments(argv); diff --git a/core/java/com/android/internal/os/WrapperInit.java b/core/java/com/android/internal/os/WrapperInit.java index f0e779694c90..790d7f7ab694 100644 --- a/core/java/com/android/internal/os/WrapperInit.java +++ b/core/java/com/android/internal/os/WrapperInit.java @@ -23,16 +23,18 @@ import android.system.Os; import android.system.OsConstants; import android.system.StructCapUserData; import android.system.StructCapUserHeader; -import android.util.TimingsTraceLog; import android.util.Slog; +import android.util.TimingsTraceLog; + import dalvik.system.VMRuntime; + +import libcore.io.IoUtils; + import java.io.DataOutputStream; import java.io.FileDescriptor; import java.io.FileOutputStream; import java.io.IOException; -import libcore.io.IoUtils; - /** * Startup class for the wrapper process. * @hide @@ -166,10 +168,10 @@ public class WrapperInit { System.arraycopy(argv, 2, removedArgs, 0, argv.length - 2); argv = removedArgs; } - // Perform the same initialization that would happen after the Zygote forks. Zygote.nativePreApplicationInit(); - return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader); + return RuntimeInit.applicationInit(targetSdkVersion, /*disabledCompatChanges*/ null, + argv, classLoader); } /** diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java index 00ab45ec3537..33adec106d97 100644 --- a/core/java/com/android/internal/os/Zygote.java +++ b/core/java/com/android/internal/os/Zygote.java @@ -642,6 +642,7 @@ public final class Zygote { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); return ZygoteInit.zygoteInit(args.mTargetSdkVersion, + args.mDisabledCompatChanges, args.mRemainingArgs, null /* classLoader */); } finally { diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java index fc55ccf4d6b7..3915ba273dfa 100644 --- a/core/java/com/android/internal/os/ZygoteArguments.java +++ b/core/java/com/android/internal/os/ZygoteArguments.java @@ -210,6 +210,12 @@ class ZygoteArguments { int mHiddenApiAccessStatslogSampleRate = -1; /** + * A set of disabled app compatibility changes for the running app. From + * --disabled-compat-changes. + */ + long[] mDisabledCompatChanges = null; + + /** * Constructs instance and parses args * * @param args zygote command-line args @@ -416,6 +422,16 @@ class ZygoteArguments { mUsapPoolStatusSpecified = true; mUsapPoolEnabled = Boolean.parseBoolean(arg.substring(arg.indexOf('=') + 1)); expectRuntimeArgs = false; + } else if (arg.startsWith("--disabled-compat-changes=")) { + if (mDisabledCompatChanges != null) { + throw new IllegalArgumentException("Duplicate arg specified"); + } + final String[] params = arg.substring(arg.indexOf('=') + 1).split(","); + final int length = params.length; + mDisabledCompatChanges = new long[length]; + for (int i = 0; i < length; i++) { + mDisabledCompatChanges[i] = Long.parseLong(params[i]); + } } else { break; } diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java index b15e1efa46c8..4c37591d4a66 100644 --- a/core/java/com/android/internal/os/ZygoteConnection.java +++ b/core/java/com/android/internal/os/ZygoteConnection.java @@ -502,6 +502,7 @@ class ZygoteConnection { } else { if (!isZygote) { return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion, + parsedArgs.mDisabledCompatChanges, parsedArgs.mRemainingArgs, null /* classLoader */); } else { return ZygoteInit.childZygoteInit(parsedArgs.mTargetSdkVersion, diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 93e61020ff19..7b77a92e3d3a 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -547,6 +547,7 @@ public class ZygoteInit { * Pass the remaining arguments to SystemServer. */ return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion, + parsedArgs.mDisabledCompatChanges, parsedArgs.mRemainingArgs, cl); } @@ -972,14 +973,16 @@ public class ZygoteInit { * * Current recognized args: * <ul> - * <li> <code> [--] <start class name> <args> + * <li> <code> [--] <start class name> <args> * </ul> * * @param targetSdkVersion target SDK version - * @param argv arg strings + * @param disabledCompatChanges set of disabled compat changes for the process (all others + * are enabled) + * @param argv arg strings */ - public static final Runnable zygoteInit(int targetSdkVersion, String[] argv, - ClassLoader classLoader) { + public static final Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges, + String[] argv, ClassLoader classLoader) { if (RuntimeInit.DEBUG) { Slog.d(RuntimeInit.TAG, "RuntimeInit: Starting application from zygote"); } @@ -989,7 +992,8 @@ public class ZygoteInit { RuntimeInit.commonInit(); ZygoteInit.nativeZygoteInit(); - return RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader); + return RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges, argv, + classLoader); } /** diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java index b906d84adf52..ed613c36b89b 100644 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java @@ -176,14 +176,12 @@ public class BluetoothTestUtils extends Assert { mDevice.setPin(mPin); break; case BluetoothDevice.PAIRING_VARIANT_PASSKEY: - mDevice.setPasskey(mPasskey); break; case BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION: case BluetoothDevice.PAIRING_VARIANT_CONSENT: mDevice.setPairingConfirmation(true); break; case BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT: - mDevice.setRemoteOutOfBandData(); break; } } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(intent.getAction())) { diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 1670d49a46c4..4d4f4476a974 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -24,6 +24,7 @@ android_test { ], static_libs: [ "frameworks-base-testutils", + "core-test-rules", // for libcore.dalvik.system.CloseGuardSupport "core-tests-support", "android-common", "frameworks-core-util-lib", diff --git a/core/tests/coretests/src/android/util/CloseGuardTest.java b/core/tests/coretests/src/android/util/CloseGuardTest.java new file mode 100644 index 000000000000..d86c7b79fad6 --- /dev/null +++ b/core/tests/coretests/src/android/util/CloseGuardTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +import libcore.dalvik.system.CloseGuardSupport; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; + +/** Unit tests for {@link android.util.CloseGuard} */ +public class CloseGuardTest { + + @Rule + public final TestRule rule = CloseGuardSupport.getRule(); + + @Test + public void testEnabled_NotOpen() throws Throwable { + ResourceOwner owner = new ResourceOwner(); + assertUnreleasedResources(owner, 0); + } + + @Test + public void testEnabled_OpenNotClosed() throws Throwable { + ResourceOwner owner = new ResourceOwner(); + owner.open(); + assertUnreleasedResources(owner, 1); + } + + @Test + public void testEnabled_OpenThenClosed() throws Throwable { + ResourceOwner owner = new ResourceOwner(); + owner.open(); + owner.close(); + assertUnreleasedResources(owner, 0); + } + + @Test(expected = NullPointerException.class) + public void testOpen_withNullMethodName_throwsNPE() throws Throwable { + CloseGuard closeGuard = new CloseGuard(); + closeGuard.open(null); + } + + private void assertUnreleasedResources(ResourceOwner owner, int expectedCount) + throws Throwable { + try { + CloseGuardSupport.getFinalizerChecker().accept(owner, expectedCount); + } finally { + // Close the resource so that CloseGuard does not generate a warning for real when it + // is actually finalized. + owner.close(); + } + } + + /** + * A test user of {@link CloseGuard}. + */ + private static class ResourceOwner { + + private final CloseGuard mCloseGuard; + + ResourceOwner() { + mCloseGuard = new CloseGuard(); + } + + public void open() { + mCloseGuard.open("close"); + } + + public void close() { + mCloseGuard.close(); + } + + /** + * Make finalize public so that it can be tested directly without relying on garbage + * collection to trigger it. + */ + @Override + public void finalize() throws Throwable { + mCloseGuard.warnIfOpen(); + super.finalize(); + } + } +} diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 56be3bbbc226..abfee1d415d6 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -164,9 +164,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> public void disconnect() { synchronized (mProfileLock) { - for (LocalBluetoothProfile profile : mProfiles) { - disconnect(profile); - } + mLocalAdapter.disconnectAllEnabledProfiles(mDevice); } // Disconnect PBAP server in case its connected // This is to ensure all the profiles are disconnected as some CK/Hs do not @@ -645,12 +643,8 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> refresh(); - if (bondState == BluetoothDevice.BOND_BONDED) { - if (mDevice.isBluetoothDock()) { - onBondingDockConnect(); - } else if (mDevice.isBondingInitiatedLocally()) { - connect(false); - } + if (bondState == BluetoothDevice.BOND_BONDED && mDevice.isBondingInitiatedLocally()) { + connect(false); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS index 7162121330ef..387bae130374 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OWNERS @@ -1,8 +1,7 @@ # Default reviewers for this and subdirectories. -asapperstein@google.com -asargent@google.com -eisenbach@google.com -jackqdyulei@google.com siyuanh@google.com +hughchen@google.com +timhypeng@google.com +robertluo@google.com -# Emergency approvers in case the above are not available
\ No newline at end of file +# Emergency approvers in case the above are not available diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp index 7e8721d3cc70..3c953b348ed9 100644 --- a/packages/Tethering/Android.bp +++ b/packages/Tethering/Android.bp @@ -31,6 +31,7 @@ java_defaults { "android.hardware.tetheroffload.control-V1.0-java", "tethering-client", ], + libs: ["unsupportedappusage"], manifest: "AndroidManifestBase.xml", } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 753c1171aeb3..b71943504bf6 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -5573,7 +5573,7 @@ public class ConnectivityService extends IConnectivityManager.Stub * @param linkProperties the initial link properties of this network. They can be updated * later : see {@link #updateLinkProperties}. * @param networkCapabilities the initial capabilites of this network. They can be updated - * later : see {@link #updateNetworkCapabilities}. + * later : see {@link #updateCapabilities}. * @param currentScore the initial score of the network. See * {@link NetworkAgentInfo#getCurrentScore}. * @param networkMisc metadata about the network. This is never updated. @@ -5596,7 +5596,7 @@ public class ConnectivityService extends IConnectivityManager.Stub ns, mContext, mTrackerHandler, new NetworkMisc(networkMisc), this, mNetd, mDnsResolver, mNMS, factorySerialNumber); // Make sure the network capabilities reflect what the agent info says. - nai.setNetworkCapabilities(mixInCapabilities(nai, nc)); + nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc)); final String extraInfo = networkInfo.getExtraInfo(); final String name = TextUtils.isEmpty(extraInfo) ? nai.networkCapabilities.getSSID() : extraInfo; @@ -5950,11 +5950,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - final NetworkCapabilities prevNc; - synchronized (nai) { - prevNc = nai.networkCapabilities; - nai.setNetworkCapabilities(newNc); - } + final NetworkCapabilities prevNc = nai.getAndSetNetworkCapabilities(newNc); updateUids(nai, prevNc, newNc); @@ -5963,7 +5959,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // the change we're processing can't affect any requests, it can only affect the listens // on this network. We might have been called by rematchNetworkAndRequests when a // network changed foreground state. - processListenRequests(nai, true); + processListenRequests(nai); } else { // If the requestable capabilities have changed or the score changed, we can't have been // called by rematchNetworkAndRequests, so it's safe to start a rematch. @@ -6271,8 +6267,14 @@ public class ConnectivityService extends IConnectivityManager.Stub updateAllVpnsCapabilities(); } - private void processListenRequests(NetworkAgentInfo nai, boolean capabilitiesChanged) { + private void processListenRequests(@NonNull final NetworkAgentInfo nai) { // For consistency with previous behaviour, send onLost callbacks before onAvailable. + processNewlyLostListenRequests(nai); + notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED); + processNewlySatisfiedListenRequests(nai); + } + + private void processNewlyLostListenRequests(@NonNull final NetworkAgentInfo nai) { for (NetworkRequestInfo nri : mNetworkRequests.values()) { NetworkRequest nr = nri.request; if (!nr.isListen()) continue; @@ -6281,11 +6283,9 @@ public class ConnectivityService extends IConnectivityManager.Stub callCallbackForRequest(nri, nai, ConnectivityManager.CALLBACK_LOST, 0); } } + } - if (capabilitiesChanged) { - notifyNetworkCallbacks(nai, ConnectivityManager.CALLBACK_CAP_CHANGED); - } - + private void processNewlySatisfiedListenRequests(@NonNull final NetworkAgentInfo nai) { for (NetworkRequestInfo nri : mNetworkRequests.values()) { NetworkRequest nr = nri.request; if (!nr.isListen()) continue; @@ -6468,19 +6468,20 @@ public class ConnectivityService extends IConnectivityManager.Stub // before LegacyTypeTracker sends legacy broadcasts for (NetworkRequestInfo nri : addedRequests) notifyNetworkAvailable(newNetwork, nri); - // Second pass: process all listens. - if (wasBackgroundNetwork != newNetwork.isBackgroundNetwork()) { - // TODO : most of the following is useless because the only thing that changed - // here is whether the network is a background network. Clean this up. + // Finally, process listen requests and update capabilities if the background state has + // changed for this network. For consistency with previous behavior, send onLost callbacks + // before onAvailable. + processNewlyLostListenRequests(newNetwork); - NetworkCapabilities newNc = mixInCapabilities(newNetwork, + // Maybe the network changed background states. Update its capabilities. + final boolean backgroundChanged = wasBackgroundNetwork != newNetwork.isBackgroundNetwork(); + if (backgroundChanged) { + final NetworkCapabilities newNc = mixInCapabilities(newNetwork, newNetwork.networkCapabilities); - if (Objects.equals(newNetwork.networkCapabilities, newNc)) return; - final int oldPermission = getNetworkPermission(newNetwork.networkCapabilities); final int newPermission = getNetworkPermission(newNc); - if (oldPermission != newPermission && newNetwork.created && !newNetwork.isVPN()) { + if (oldPermission != newPermission) { try { mNMS.setNetworkPermission(newNetwork.network.netId, newPermission); } catch (RemoteException e) { @@ -6488,53 +6489,11 @@ public class ConnectivityService extends IConnectivityManager.Stub } } - final NetworkCapabilities prevNc; - synchronized (newNetwork) { - prevNc = newNetwork.networkCapabilities; - newNetwork.setNetworkCapabilities(newNc); - } - - updateUids(newNetwork, prevNc, newNc); - - if (newNetwork.getCurrentScore() == score - && newNc.equalRequestableCapabilities(prevNc)) { - // If the requestable capabilities haven't changed, and the score hasn't changed, - // then the change we're processing can't affect any requests, it can only affect - // the listens on this network. - processListenRequests(newNetwork, true); - } else { - rematchAllNetworksAndRequests(); - notifyNetworkCallbacks(newNetwork, ConnectivityManager.CALLBACK_CAP_CHANGED); - } - - if (prevNc != null) { - final boolean oldMetered = prevNc.isMetered(); - final boolean newMetered = newNc.isMetered(); - final boolean meteredChanged = oldMetered != newMetered; - - if (meteredChanged) { - maybeNotifyNetworkBlocked(newNetwork, oldMetered, newMetered, - mRestrictBackground, mRestrictBackground); - } - - final boolean roamingChanged = prevNc.hasCapability(NET_CAPABILITY_NOT_ROAMING) - != newNc.hasCapability(NET_CAPABILITY_NOT_ROAMING); - - // Report changes that are interesting for network statistics tracking. - if (meteredChanged || roamingChanged) { - notifyIfacesChangedForNetworkStats(); - } - } - - if (!newNc.hasTransport(TRANSPORT_VPN)) { - // Tell VPNs about updated capabilities, since they may need to - // bubble those changes through. - updateAllVpnsCapabilities(); - } - - } else { - processListenRequests(newNetwork, false); + newNetwork.getAndSetNetworkCapabilities(newNc); + notifyNetworkCallbacks(newNetwork, ConnectivityManager.CALLBACK_CAP_CHANGED); } + + processNewlySatisfiedListenRequests(newNetwork); } /** @@ -6719,9 +6678,8 @@ public class ConnectivityService extends IConnectivityManager.Stub // NetworkCapabilities need to be set before sending the private DNS config to // NetworkMonitor, otherwise NetworkMonitor cannot determine if validation is required. - synchronized (networkAgent) { - networkAgent.setNetworkCapabilities(networkAgent.networkCapabilities); - } + networkAgent.getAndSetNetworkCapabilities(networkAgent.networkCapabilities); + handlePerNetworkPrivateDnsConfig(networkAgent, mDnsManager.getPrivateDnsConfig()); updateLinkProperties(networkAgent, new LinkProperties(networkAgent.linkProperties), null); diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 840b7af19890..0d496b6b427d 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -58,6 +58,7 @@ import android.net.NetworkStack; import android.net.NetworkStats; import android.net.NetworkUtils; import android.net.RouteInfo; +import android.net.TetherConfigParcel; import android.net.TetherStatsParcel; import android.net.UidRange; import android.net.UidRangeParcel; @@ -1023,7 +1024,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub { NetworkStack.checkNetworkStackPermission(mContext); // an odd number of addrs will fail try { - mNetdService.tetherStartWithConfiguration(usingLegacyDnsProxy, dhcpRange); + final TetherConfigParcel config = new TetherConfigParcel(); + config.usingLegacyDnsProxy = usingLegacyDnsProxy; + config.dhcpRanges = dhcpRange; + mNetdService.tetherStartWithConfiguration(config); } catch (RemoteException | ServiceSpecificException e) { throw new IllegalStateException(e); } diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 48e8e964f41c..be95456bfef6 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -449,9 +449,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mOtaspMode[i] = TelephonyManager.OTASP_UNKNOWN; mCallDisconnectCause[i] = DisconnectCause.NOT_VALID; mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID; - mCallQuality[i] = new CallQuality(); + mCallQuality[i] = createCallQuality(); mCallAttributes[i] = new CallAttributes(new PreciseCallState(), - TelephonyManager.NETWORK_TYPE_UNKNOWN, new CallQuality()); + TelephonyManager.NETWORK_TYPE_UNKNOWN, createCallQuality()); mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN; mPreciseCallState[i] = new PreciseCallState(); mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE; @@ -546,9 +546,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mOtaspMode[i] = TelephonyManager.OTASP_UNKNOWN; mCallDisconnectCause[i] = DisconnectCause.NOT_VALID; mCallPreciseDisconnectCause[i] = PreciseDisconnectCause.NOT_VALID; - mCallQuality[i] = new CallQuality(); + mCallQuality[i] = createCallQuality(); mCallAttributes[i] = new CallAttributes(new PreciseCallState(), - TelephonyManager.NETWORK_TYPE_UNKNOWN, new CallQuality()); + TelephonyManager.NETWORK_TYPE_UNKNOWN, createCallQuality()); mCallNetworkType[i] = TelephonyManager.NETWORK_TYPE_UNKNOWN; mPreciseCallState[i] = new PreciseCallState(); mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE; @@ -1610,8 +1610,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } broadcastDataConnectionStateChanged(state, isDataAllowed, apn, apnType, linkProperties, networkCapabilities, roaming, subId); - broadcastPreciseDataConnectionStateChanged(state, networkType, apnType, apn, - linkProperties, DataFailCause.NONE); } public void notifyDataConnectionFailed(String apnType) { @@ -1651,9 +1649,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { handleRemoveListLocked(); } broadcastDataConnectionFailed(apnType, subId); - broadcastPreciseDataConnectionStateChanged(TelephonyManager.DATA_UNKNOWN, - TelephonyManager.NETWORK_TYPE_UNKNOWN, apnType, null, null, - DataFailCause.NONE); } public void notifyCellLocation(Bundle cellLocation) { @@ -1743,7 +1738,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { if (mPreciseCallState[phoneId].getForegroundCallState() != PreciseCallState.PRECISE_CALL_STATE_ACTIVE) { mCallNetworkType[phoneId] = TelephonyManager.NETWORK_TYPE_UNKNOWN; - mCallQuality[phoneId] = new CallQuality(); + mCallQuality[phoneId] = createCallQuality(); } mCallAttributes[phoneId] = new CallAttributes(mPreciseCallState[phoneId], mCallNetworkType[phoneId], mCallQuality[phoneId]); @@ -1771,8 +1766,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { } handleRemoveListLocked(); } - broadcastPreciseCallStateChanged(ringingCallState, foregroundCallState, - backgroundCallState); } public void notifyDisconnectCause(int phoneId, int subId, int disconnectCause, @@ -1854,8 +1847,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { handleRemoveListLocked(); } - broadcastPreciseDataConnectionStateChanged(TelephonyManager.DATA_UNKNOWN, - TelephonyManager.NETWORK_TYPE_UNKNOWN, apnType, apn, null, failCause); } @Override @@ -2347,33 +2338,6 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL); } - private void broadcastPreciseCallStateChanged(int ringingCallState, int foregroundCallState, - int backgroundCallState) { - Intent intent = new Intent(TelephonyManager.ACTION_PRECISE_CALL_STATE_CHANGED); - intent.putExtra(TelephonyManager.EXTRA_RINGING_CALL_STATE, ringingCallState); - intent.putExtra(TelephonyManager.EXTRA_FOREGROUND_CALL_STATE, foregroundCallState); - intent.putExtra(TelephonyManager.EXTRA_BACKGROUND_CALL_STATE, backgroundCallState); - mContext.sendBroadcastAsUser(intent, UserHandle.ALL, - android.Manifest.permission.READ_PRECISE_PHONE_STATE); - } - - private void broadcastPreciseDataConnectionStateChanged(int state, int networkType, - String apnType, String apn, LinkProperties linkProperties, - @DataFailureCause int failCause) { - Intent intent = new Intent(TelephonyManager.ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED); - intent.putExtra(TelephonyManager.EXTRA_STATE, state); - intent.putExtra(PhoneConstants.DATA_NETWORK_TYPE_KEY, networkType); - if (apnType != null) intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY, apnType); - if (apn != null) intent.putExtra(PhoneConstants.DATA_APN_KEY, apn); - if (linkProperties != null) { - intent.putExtra(PhoneConstants.DATA_LINK_PROPERTIES_KEY, linkProperties); - } - intent.putExtra(PhoneConstants.DATA_FAILURE_CAUSE_KEY, failCause); - - mContext.sendBroadcastAsUser(intent, UserHandle.ALL, - android.Manifest.permission.READ_PRECISE_PHONE_STATE); - } - private void enforceNotifyPermissionOrCarrierPrivilege(String method) { if (checkNotifyPermission()) { return; @@ -2769,4 +2733,9 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub { return "UNKNOWN"; } } + + /** Returns a new CallQuality object with default values. */ + private static CallQuality createCallQuality() { + return new CallQuality(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 5ffdf02ef11e..35774ed3ca6f 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -2411,7 +2411,8 @@ public class ActivityManagerService extends IActivityManager.Stub mConstants = hasHandlerThread ? new ActivityManagerConstants(mContext, this, mHandler) : null; final ActiveUids activeUids = new ActiveUids(this, false /* postChangesToAtm */); - mProcessList.init(this, activeUids); + mPlatformCompat = null; + mProcessList.init(this, activeUids, mPlatformCompat); mLowMemDetector = null; mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids); @@ -2432,7 +2433,6 @@ public class ActivityManagerService extends IActivityManager.Stub mProcStartHandler = null; mHiddenApiBlacklist = null; mFactoryTest = FACTORY_TEST_OFF; - mPlatformCompat = null; } // Note: This method is invoked on the main thread but may need to attach various @@ -2461,7 +2461,9 @@ public class ActivityManagerService extends IActivityManager.Stub mConstants = new ActivityManagerConstants(mContext, this, mHandler); final ActiveUids activeUids = new ActiveUids(this, true /* postChangesToAtm */); - mProcessList.init(this, activeUids); + mPlatformCompat = (PlatformCompat) ServiceManager.getService( + Context.PLATFORM_COMPAT_SERVICE); + mProcessList.init(this, activeUids, mPlatformCompat); mLowMemDetector = new LowMemDetector(this); mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids); @@ -2569,9 +2571,6 @@ public class ActivityManagerService extends IActivityManager.Stub mHiddenApiBlacklist = new HiddenApiSettings(mHandler, mContext); - mPlatformCompat = (PlatformCompat) ServiceManager.getService( - Context.PLATFORM_COMPAT_SERVICE); - Watchdog.getInstance().addMonitor(this); Watchdog.getInstance().addThread(mHandler); @@ -5048,9 +5047,7 @@ public class ActivityManagerService extends IActivityManager.Stub bindApplicationTimeMillis = SystemClock.elapsedRealtime(); mAtmInternal.preBindApplication(app.getWindowProcessController()); final ActiveInstrumentation instr2 = app.getActiveInstrumentation(); - long[] disabledCompatChanges = {}; if (mPlatformCompat != null) { - disabledCompatChanges = mPlatformCompat.getDisabledChanges(app.info); mPlatformCompat.resetReporting(app.info); } if (app.isolatedEntryPoint != null) { @@ -5069,7 +5066,7 @@ public class ActivityManagerService extends IActivityManager.Stub app.compat, getCommonServicesLocked(app.isolated), mCoreSettingsObserver.getCoreSettingsLocked(), buildSerial, autofillOptions, contentCaptureOptions, - disabledCompatChanges); + app.mDisabledCompatChanges); } else { thread.bindApplication(processName, appInfo, providers, null, profilerInfo, null, null, null, testMode, @@ -5079,7 +5076,7 @@ public class ActivityManagerService extends IActivityManager.Stub app.compat, getCommonServicesLocked(app.isolated), mCoreSettingsObserver.getCoreSettingsLocked(), buildSerial, autofillOptions, contentCaptureOptions, - disabledCompatChanges); + app.mDisabledCompatChanges); } if (profilerInfo != null) { profilerInfo.closeFd(); diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index a2670d8dd424..f397a3243b2a 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -96,6 +96,7 @@ import com.android.internal.util.MemInfoReader; import com.android.server.LocalServices; import com.android.server.ServiceThread; import com.android.server.Watchdog; +import com.android.server.compat.PlatformCompat; import com.android.server.pm.dex.DexManager; import com.android.server.wm.ActivityServiceConnectionsHolder; import com.android.server.wm.WindowManagerService; @@ -384,6 +385,8 @@ public final class ProcessList { final ArrayMap<AppZygote, ArrayList<ProcessRecord>> mAppZygoteProcesses = new ArrayMap<AppZygote, ArrayList<ProcessRecord>>(); + private PlatformCompat mPlatformCompat = null; + final class IsolatedUidRange { @VisibleForTesting public final int mFirstUid; @@ -565,9 +568,11 @@ public final class ProcessList { updateOomLevels(0, 0, false); } - void init(ActivityManagerService service, ActiveUids activeUids) { + void init(ActivityManagerService service, ActiveUids activeUids, + PlatformCompat platformCompat) { mService = service; mActiveUids = activeUids; + mPlatformCompat = platformCompat; if (sKillHandler == null) { sKillThread = new ServiceThread(TAG + ":kill", @@ -1657,6 +1662,10 @@ public final class ProcessList { Slog.wtf(TAG, "startProcessLocked processName:" + app.processName + " with non-zero pid:" + app.pid); } + app.mDisabledCompatChanges = null; + if (mPlatformCompat != null) { + app.mDisabledCompatChanges = mPlatformCompat.getDisabledChanges(app.info); + } final long startSeq = app.startSeq = ++mProcStartSeqCounter; app.setStartParams(uid, hostingRecord, seInfo, startTime); app.setUsingWrapper(invokeWith != null @@ -1811,8 +1820,8 @@ public final class ProcessList { startResult = startWebView(entryPoint, app.processName, uid, uid, gids, runtimeFlags, mountExternal, app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet, - app.info.dataDir, null, app.info.packageName, - new String[] {PROC_START_SEQ_IDENT + app.startSeq}); + app.info.dataDir, null, app.info.packageName, app.mDisabledCompatChanges, + new String[]{PROC_START_SEQ_IDENT + app.startSeq}); } else if (hostingRecord.usesAppZygote()) { final AppZygote appZygote = createAppZygoteForProcessIfNeeded(app); @@ -1820,14 +1829,15 @@ public final class ProcessList { app.processName, uid, uid, gids, runtimeFlags, mountExternal, app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet, app.info.dataDir, null, app.info.packageName, - /*useUsapPool=*/ false, - new String[] {PROC_START_SEQ_IDENT + app.startSeq}); + /*useUsapPool=*/ false, app.mDisabledCompatChanges, + new String[]{PROC_START_SEQ_IDENT + app.startSeq}); } else { startResult = Process.start(entryPoint, app.processName, uid, uid, gids, runtimeFlags, mountExternal, app.info.targetSdkVersion, seInfo, requiredAbi, instructionSet, app.info.dataDir, invokeWith, app.info.packageName, - new String[] {PROC_START_SEQ_IDENT + app.startSeq}); + app.mDisabledCompatChanges, + new String[]{PROC_START_SEQ_IDENT + app.startSeq}); } checkSlow(startTime, "startProcess: returned from zygote!"); return startResult; diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index ea3084274ae0..8c1a0d3abcd2 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -307,6 +307,8 @@ class ProcessRecord implements WindowProcessListener { long startTime; // This will be same as {@link #uid} usually except for some apps used during factory testing. int startUid; + // set of disabled compat changes for the process (all others are enabled) + long[] mDisabledCompatChanges; void setStartParams(int startUid, HostingRecord hostingRecord, String seInfo, long startTime) { diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 37add3da5a48..135f199d88be 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -62,9 +62,6 @@ public class AudioDeviceInventory { private @NonNull AudioDeviceBroker mDeviceBroker; - // cache of the address of the last dock the device was connected to - private String mDockAddress; - // Monitoring of audio routes. Protected by mAudioRoutes. final AudioRoutesInfo mCurAudioRoutes = new AudioRoutesInfo(); final RemoteCallbackList<IAudioRoutesObserver> mRoutesObservers = @@ -168,7 +165,7 @@ public class AudioDeviceInventory { int a2dpVolume = btInfo.getVolume(); if (AudioService.DEBUG_DEVICES) { Log.d(TAG, "onSetA2dpSinkConnectionState btDevice=" + btDevice + " state=" - + state + " is dock=" + btDevice.isBluetoothDock() + " vol=" + a2dpVolume); + + state + " vol=" + a2dpVolume); } String address = btDevice.getAddress(); if (!BluetoothAdapter.checkBluetoothAddress(address)) { @@ -196,42 +193,17 @@ public class AudioDeviceInventory { mDeviceBroker.postBluetoothA2dpDeviceConfigChange(btDevice); } } else { - if (btDevice.isBluetoothDock()) { - if (state == BluetoothProfile.STATE_DISCONNECTED) { - // introduction of a delay for transient disconnections of docks when - // power is rapidly turned off/on, this message will be canceled if - // we reconnect the dock under a preset delay - makeA2dpDeviceUnavailableLater(address, - AudioDeviceBroker.BTA2DP_DOCK_TIMEOUT_MS); - // the next time isConnected is evaluated, it will be false for the dock - } - } else { - makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat); - } + makeA2dpDeviceUnavailableNow(address, di.mDeviceCodecFormat); } - } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) { - if (btDevice.isBluetoothDock()) { - // this could be a reconnection after a transient disconnection - mDeviceBroker.cancelA2dpDockTimeout(); - mDockAddress = address; - } else { - // this could be a connection of another A2DP device before the timeout of - // a dock: cancel the dock timeout, and make the dock unavailable now - if (mDeviceBroker.hasScheduledA2dpDockTimeout() && mDockAddress != null) { - mDeviceBroker.cancelA2dpDockTimeout(); - makeA2dpDeviceUnavailableNow(mDockAddress, - AudioSystem.AUDIO_FORMAT_DEFAULT); - } - } - if (a2dpVolume != -1) { - mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC, - // convert index to internal representation in VolumeStreamState - a2dpVolume * 10, - AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "onSetA2dpSinkConnectionState"); - } - makeA2dpDeviceAvailable(address, BtHelper.getName(btDevice), - "onSetA2dpSinkConnectionState", a2dpCodec); } + if (a2dpVolume != -1) { + mDeviceBroker.postSetVolumeIndexOnDevice(AudioSystem.STREAM_MUSIC, + // convert index to internal representation in VolumeStreamState + a2dpVolume * 10, + AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "onSetA2dpSinkConnectionState"); + } + makeA2dpDeviceAvailable(address, BtHelper.getName(btDevice), + "onSetA2dpSinkConnectionState", a2dpCodec); } } @@ -672,9 +644,6 @@ public class AudioDeviceInventory { DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, address)); // Remove A2DP routes as well setCurrentAudioRouteNameIfPossible(null); - if (mDockAddress == address) { - mDockAddress = null; - } } @GuardedBy("mConnectedDevices") diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java index bb7f86233a40..5e085ca293a4 100644 --- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java +++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java @@ -291,13 +291,18 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> { * * <p>If {@link NetworkMonitor#notifyNetworkCapabilitiesChanged(NetworkCapabilities)} fails, * the exception is logged but not reported to callers. + * + * @return the old capabilities of this network. */ - public void setNetworkCapabilities(NetworkCapabilities nc) { + public synchronized NetworkCapabilities getAndSetNetworkCapabilities( + @NonNull final NetworkCapabilities nc) { + final NetworkCapabilities oldNc = networkCapabilities; networkCapabilities = nc; final NetworkMonitorManager nm = mNetworkMonitor; if (nm != null) { nm.notifyNetworkCapabilitiesChanged(nc); } + return oldNc; } public ConnectivityService connService() { diff --git a/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java b/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java index 52cede2121bd..23b5c1411b0e 100644 --- a/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java +++ b/services/core/java/com/android/server/hdmi/HdmiCecKeycode.java @@ -250,7 +250,11 @@ final class HdmiCecKeycode { new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_LEFT_UP), // No Android keycode defined for CEC_KEYCODE_LEFT_DOWN new KeycodeEntry(UNSUPPORTED_KEYCODE, CEC_KEYCODE_LEFT_DOWN), + // Both KEYCODE_HOME and KEYCODE_MENU keys are sent as CEC_KEYCODE_ROOT_MENU + // NOTE that the HOME key is not usually forwarded. + // When CEC_KEYCODE_ROOT_MENU is received, it is translated to KEYCODE_HOME new KeycodeEntry(KeyEvent.KEYCODE_HOME, CEC_KEYCODE_ROOT_MENU), + new KeycodeEntry(KeyEvent.KEYCODE_MENU, CEC_KEYCODE_ROOT_MENU), new KeycodeEntry(KeyEvent.KEYCODE_SETTINGS, CEC_KEYCODE_SETUP_MENU), new KeycodeEntry(KeyEvent.KEYCODE_TV_CONTENTS_MENU, CEC_KEYCODE_CONTENTS_MENU, false), // No Android keycode defined for CEC_KEYCODE_FAVORITE_MENU diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java index 181a43549313..3fc843810313 100644 --- a/services/core/java/com/android/server/location/GnssLocationProvider.java +++ b/services/core/java/com/android/server/location/GnssLocationProvider.java @@ -508,8 +508,10 @@ public class GnssLocationProvider extends AbstractLocationProvider implements CarrierConfigManager configManager = (CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE); int ddSubId = SubscriptionManager.getDefaultDataSubscriptionId(); - String mccMnc = SubscriptionManager.isValidSubscriptionId(ddSubId) - ? phone.getSimOperator(ddSubId) : phone.getSimOperator(); + if (SubscriptionManager.isValidSubscriptionId(ddSubId)) { + phone = phone.createForSubscriptionId(ddSubId); + } + String mccMnc = phone.getSimOperator(); boolean isKeepLppProfile = false; if (!TextUtils.isEmpty(mccMnc)) { if (DEBUG) Log.d(TAG, "SIM MCC/MNC is available: " + mccMnc); @@ -1903,24 +1905,17 @@ public class GnssLocationProvider extends AbstractLocationProvider implements String setId = null; int ddSubId = SubscriptionManager.getDefaultDataSubscriptionId(); + if (SubscriptionManager.isValidSubscriptionId(ddSubId)) { + phone = phone.createForSubscriptionId(ddSubId); + } if ((flags & AGPS_RIL_REQUEST_SETID_IMSI) == AGPS_RIL_REQUEST_SETID_IMSI) { - if (SubscriptionManager.isValidSubscriptionId(ddSubId)) { - setId = phone.getSubscriberId(ddSubId); - } - if (setId == null) { - setId = phone.getSubscriberId(); - } + setId = phone.getSubscriberId(); if (setId != null) { // This means the framework has the SIM card. type = AGPS_SETID_TYPE_IMSI; } } else if ((flags & AGPS_RIL_REQUEST_SETID_MSISDN) == AGPS_RIL_REQUEST_SETID_MSISDN) { - if (SubscriptionManager.isValidSubscriptionId(ddSubId)) { - setId = phone.getLine1Number(ddSubId); - } - if (setId == null) { - setId = phone.getLine1Number(); - } + setId = phone.getLine1Number(); if (setId != null) { // This means the framework has the SIM card. type = AGPS_SETID_TYPE_MSISDN; diff --git a/services/core/java/com/android/server/notification/NotificationComparator.java b/services/core/java/com/android/server/notification/NotificationComparator.java index 9b9f4de7a18f..f295ed37939c 100644 --- a/services/core/java/com/android/server/notification/NotificationComparator.java +++ b/services/core/java/com/android/server/notification/NotificationComparator.java @@ -23,7 +23,6 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.provider.Settings; import android.telecom.TelecomManager; import com.android.internal.util.NotificationMessagingUtil; @@ -55,14 +54,9 @@ public class NotificationComparator final boolean isLeftHighImportance = leftImportance >= IMPORTANCE_DEFAULT; final boolean isRightHighImportance = rightImportance >= IMPORTANCE_DEFAULT; - // With new interruption model, prefer importance bucket above all other criteria - // (to ensure buckets are contiguous) - if (Settings.Secure.getInt(mContext.getContentResolver(), - Settings.Secure.NOTIFICATION_NEW_INTERRUPTION_MODEL, 1) == 1) { - if (isLeftHighImportance != isRightHighImportance) { - // by importance bucket, high importance higher than low importance - return -1 * Boolean.compare(isLeftHighImportance, isRightHighImportance); - } + if (isLeftHighImportance != isRightHighImportance) { + // by importance bucket, high importance higher than low importance + return -1 * Boolean.compare(isLeftHighImportance, isRightHighImportance); } // first all colorized notifications diff --git a/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java deleted file mode 100644 index 4e8ba078a73c..000000000000 --- a/services/core/java/com/android/server/timedetector/SimpleTimeDetectorStrategy.java +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.timedetector; - -import android.annotation.IntDef; -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.AlarmManager; -import android.app.timedetector.ManualTimeSuggestion; -import android.app.timedetector.PhoneTimeSuggestion; -import android.content.Intent; -import android.util.LocalLog; -import android.util.Slog; -import android.util.TimestampedValue; - -import com.android.internal.annotations.GuardedBy; -import com.android.internal.telephony.TelephonyIntents; -import com.android.internal.util.IndentingPrintWriter; - -import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * An implementation of TimeDetectorStrategy that passes only NITZ suggestions to - * {@link AlarmManager}. - * - * <p>Most public methods are marked synchronized to ensure thread safety around internal state. - */ -public final class SimpleTimeDetectorStrategy implements TimeDetectorStrategy { - - private static final boolean DBG = false; - private static final String LOG_TAG = "SimpleTimeDetectorStrategy"; - - @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL }) - @Retention(RetentionPolicy.SOURCE) - public @interface Origin {} - - /** Used when a time value originated from a telephony signal. */ - @Origin - private static final int ORIGIN_PHONE = 1; - - /** Used when a time value originated from a user / manual settings. */ - @Origin - private static final int ORIGIN_MANUAL = 2; - - /** - * CLOCK_PARANOIA: The maximum difference allowed between the expected system clock time and the - * actual system clock time before a warning is logged. Used to help identify situations where - * there is something other than this class setting the system clock automatically. - */ - private static final long SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS = 2 * 1000; - - // A log for changes made to the system clock and why. - @NonNull - private final LocalLog mTimeChangesLog = new LocalLog(30, false /* useLocalTimestamps */); - - // @NonNull after initialize() - private Callback mCallback; - - // Last phone suggestion. - @Nullable private PhoneTimeSuggestion mLastPhoneSuggestion; - - // Information about the last time signal received: Used when toggling auto-time. - @Nullable private TimestampedValue<Long> mLastAutoSystemClockTime; - private boolean mLastAutoSystemClockTimeSendNetworkBroadcast; - - // System clock state. - @Nullable private TimestampedValue<Long> mLastAutoSystemClockTimeSet; - - @Override - public void initialize(@NonNull Callback callback) { - mCallback = callback; - } - - @Override - public synchronized void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) { - // NITZ logic - - // Empty suggestions are just ignored as we don't currently keep track of suggestion origin. - if (timeSuggestion.getUtcTime() == null) { - return; - } - - boolean timeSuggestionIsValid = - validateNewPhoneSuggestion(timeSuggestion, mLastPhoneSuggestion); - if (!timeSuggestionIsValid) { - return; - } - // Always store the last NITZ value received, regardless of whether we go on to use it to - // update the system clock. This is so that we can validate future phone suggestions. - mLastPhoneSuggestion = timeSuggestion; - - // System clock update logic. - final TimestampedValue<Long> newUtcTime = timeSuggestion.getUtcTime(); - setSystemClockIfRequired(ORIGIN_PHONE, newUtcTime, timeSuggestion); - } - - @Override - public synchronized void suggestManualTime(ManualTimeSuggestion timeSuggestion) { - final TimestampedValue<Long> newUtcTime = timeSuggestion.getUtcTime(); - setSystemClockIfRequired(ORIGIN_MANUAL, newUtcTime, timeSuggestion); - } - - private static boolean validateNewPhoneSuggestion(@NonNull PhoneTimeSuggestion newSuggestion, - @Nullable PhoneTimeSuggestion lastSuggestion) { - - if (lastSuggestion != null) { - long referenceTimeDifference = TimestampedValue.referenceTimeDifference( - newSuggestion.getUtcTime(), lastSuggestion.getUtcTime()); - if (referenceTimeDifference < 0 || referenceTimeDifference > Integer.MAX_VALUE) { - // Out of order or bogus. - Slog.w(LOG_TAG, "Bad NITZ signal received." - + " referenceTimeDifference=" + referenceTimeDifference - + " lastSuggestion=" + lastSuggestion - + " newSuggestion=" + newSuggestion); - return false; - } - } - return true; - } - - @GuardedBy("this") - private void setSystemClockIfRequired( - @Origin int origin, TimestampedValue<Long> time, Object cause) { - // Historically, Android has sent a TelephonyIntents.ACTION_NETWORK_SET_TIME broadcast only - // when setting the time using NITZ. - boolean sendNetworkBroadcast = origin == ORIGIN_PHONE; - - boolean isOriginAutomatic = isOriginAutomatic(origin); - if (isOriginAutomatic) { - // Store the last auto time candidate we've seen in all cases so we can set the system - // clock when/if time detection is off but later enabled. - mLastAutoSystemClockTime = time; - mLastAutoSystemClockTimeSendNetworkBroadcast = sendNetworkBroadcast; - - if (!mCallback.isAutoTimeDetectionEnabled()) { - if (DBG) { - Slog.d(LOG_TAG, "Auto time detection is not enabled." - + " origin=" + origin - + ", time=" + time - + ", cause=" + cause); - } - return; - } - } else { - if (mCallback.isAutoTimeDetectionEnabled()) { - if (DBG) { - Slog.d(LOG_TAG, "Auto time detection is enabled." - + " origin=" + origin - + ", time=" + time - + ", cause=" + cause); - } - return; - } - } - - mCallback.acquireWakeLock(); - try { - long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis(); - long actualTimeMillis = mCallback.systemClockMillis(); - - if (isOriginAutomatic) { - // CLOCK_PARANOIA : Check to see if this class owns the clock or if something else - // may be setting the clock. - if (mLastAutoSystemClockTimeSet != null) { - long expectedTimeMillis = TimeDetectorStrategy.getTimeAt( - mLastAutoSystemClockTimeSet, elapsedRealtimeMillis); - long absSystemClockDifference = Math.abs(expectedTimeMillis - actualTimeMillis); - if (absSystemClockDifference > SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS) { - Slog.w(LOG_TAG, - "System clock has not tracked elapsed real time clock. A clock may" - + " be inaccurate or something unexpectedly set the system" - + " clock." - + " elapsedRealtimeMillis=" + elapsedRealtimeMillis - + " expectedTimeMillis=" + expectedTimeMillis - + " actualTimeMillis=" + actualTimeMillis); - } - } - } - - adjustAndSetDeviceSystemClock( - time, sendNetworkBroadcast, elapsedRealtimeMillis, actualTimeMillis, cause); - } finally { - mCallback.releaseWakeLock(); - } - } - - private static boolean isOriginAutomatic(@Origin int origin) { - return origin == ORIGIN_PHONE; - } - - @Override - public synchronized void handleAutoTimeDetectionChanged() { - // If automatic time detection is enabled we update the system clock instantly if we can. - // Conversely, if automatic time detection is disabled we leave the clock as it is. - boolean enabled = mCallback.isAutoTimeDetectionEnabled(); - if (enabled) { - if (mLastAutoSystemClockTime != null) { - // Only send the network broadcast if the last candidate would have caused one. - final boolean sendNetworkBroadcast = mLastAutoSystemClockTimeSendNetworkBroadcast; - - mCallback.acquireWakeLock(); - try { - long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis(); - long actualTimeMillis = mCallback.systemClockMillis(); - - final String reason = "Automatic time detection enabled."; - adjustAndSetDeviceSystemClock(mLastAutoSystemClockTime, sendNetworkBroadcast, - elapsedRealtimeMillis, actualTimeMillis, reason); - } finally { - mCallback.releaseWakeLock(); - } - } - } else { - // CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict what - // it should be in future. - mLastAutoSystemClockTimeSet = null; - } - } - - @Override - public synchronized void dump(@NonNull PrintWriter pw, @Nullable String[] args) { - IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); - ipw.println("TimeDetectorStrategy:"); - ipw.increaseIndent(); // level 1 - - ipw.println("mLastPhoneSuggestion=" + mLastPhoneSuggestion); - ipw.println("mLastAutoSystemClockTimeSet=" + mLastAutoSystemClockTimeSet); - ipw.println("mLastAutoSystemClockTime=" + mLastAutoSystemClockTime); - ipw.println("mLastAutoSystemClockTimeSendNetworkBroadcast=" - + mLastAutoSystemClockTimeSendNetworkBroadcast); - - - ipw.println("Time change log:"); - ipw.increaseIndent(); // level 2 - mTimeChangesLog.dump(ipw); - ipw.decreaseIndent(); // level 2 - - ipw.decreaseIndent(); // level 1 - ipw.flush(); - } - - @GuardedBy("this") - private void adjustAndSetDeviceSystemClock( - TimestampedValue<Long> newTime, boolean sendNetworkBroadcast, - long elapsedRealtimeMillis, long actualSystemClockMillis, Object cause) { - - // Adjust for the time that has elapsed since the signal was received. - long newSystemClockMillis = TimeDetectorStrategy.getTimeAt(newTime, elapsedRealtimeMillis); - - // Check if the new signal would make sufficient difference to the system clock. If it's - // below the threshold then ignore it. - long absTimeDifference = Math.abs(newSystemClockMillis - actualSystemClockMillis); - long systemClockUpdateThreshold = mCallback.systemClockUpdateThresholdMillis(); - if (absTimeDifference < systemClockUpdateThreshold) { - if (DBG) { - Slog.d(LOG_TAG, "Not setting system clock. New time and" - + " system clock are close enough." - + " elapsedRealtimeMillis=" + elapsedRealtimeMillis - + " newTime=" + newTime - + " cause=" + cause - + " systemClockUpdateThreshold=" + systemClockUpdateThreshold - + " absTimeDifference=" + absTimeDifference); - } - return; - } - - mCallback.setSystemClock(newSystemClockMillis); - String logMsg = "Set system clock using time=" + newTime - + " cause=" + cause - + " elapsedRealtimeMillis=" + elapsedRealtimeMillis - + " newSystemClockMillis=" + newSystemClockMillis; - if (DBG) { - Slog.d(LOG_TAG, logMsg); - } - mTimeChangesLog.log(logMsg); - - // CLOCK_PARANOIA : Record the last time this class set the system clock. - mLastAutoSystemClockTimeSet = newTime; - - if (sendNetworkBroadcast) { - // Send a broadcast that telephony code used to send after setting the clock. - // TODO Remove this broadcast as soon as there are no remaining listeners. - Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME); - intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); - intent.putExtra("time", newSystemClockMillis); - mCallback.sendStickyBroadcast(intent); - } - } -} diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java index 34400ff6b484..172367a128cc 100644 --- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java +++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java @@ -31,7 +31,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; import com.android.server.FgThread; import com.android.server.SystemService; -import com.android.server.timedetector.TimeDetectorStrategy.Callback; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -58,17 +57,16 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { @NonNull private final Handler mHandler; @NonNull private final Context mContext; - @NonNull private final Callback mCallback; @NonNull private final TimeDetectorStrategy mTimeDetectorStrategy; private static TimeDetectorService create(@NonNull Context context) { - TimeDetectorStrategy timeDetector = new SimpleTimeDetectorStrategy(); + TimeDetectorStrategy timeDetectorStrategy = new TimeDetectorStrategyImpl(); TimeDetectorStrategyCallbackImpl callback = new TimeDetectorStrategyCallbackImpl(context); - timeDetector.initialize(callback); + timeDetectorStrategy.initialize(callback); Handler handler = FgThread.getHandler(); TimeDetectorService timeDetectorService = - new TimeDetectorService(context, handler, callback, timeDetector); + new TimeDetectorService(context, handler, timeDetectorStrategy); // Wire up event listening. ContentResolver contentResolver = context.getContentResolver(); @@ -85,10 +83,9 @@ public final class TimeDetectorService extends ITimeDetectorService.Stub { @VisibleForTesting public TimeDetectorService(@NonNull Context context, @NonNull Handler handler, - @NonNull Callback callback, @NonNull TimeDetectorStrategy timeDetectorStrategy) { + @NonNull TimeDetectorStrategy timeDetectorStrategy) { mContext = Objects.requireNonNull(context); mHandler = Objects.requireNonNull(handler); - mCallback = Objects.requireNonNull(callback); mTimeDetectorStrategy = Objects.requireNonNull(timeDetectorStrategy); } diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java new file mode 100644 index 000000000000..1b1ac6d3ed07 --- /dev/null +++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java @@ -0,0 +1,549 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.timedetector; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.AlarmManager; +import android.app.timedetector.ManualTimeSuggestion; +import android.app.timedetector.PhoneTimeSuggestion; +import android.content.Intent; +import android.util.ArrayMap; +import android.util.LocalLog; +import android.util.Slog; +import android.util.TimestampedValue; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.telephony.TelephonyIntents; +import com.android.internal.util.IndentingPrintWriter; + +import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.LinkedList; +import java.util.Map; + +/** + * An implementation of TimeDetectorStrategy that passes phone and manual suggestions to + * {@link AlarmManager}. When there are multiple phone sources, the one with the lowest ID is used + * unless the data becomes too stale. + * + * <p>Most public methods are marked synchronized to ensure thread safety around internal state. + */ +public final class TimeDetectorStrategyImpl implements TimeDetectorStrategy { + + private static final boolean DBG = false; + private static final String LOG_TAG = "SimpleTimeDetectorStrategy"; + + /** A score value used to indicate "no score", either due to validation failure or age. */ + private static final int PHONE_INVALID_SCORE = -1; + /** The number of buckets phone suggestions can be put in by age. */ + private static final int PHONE_BUCKET_COUNT = 24; + /** Each bucket is this size. All buckets are equally sized. */ + @VisibleForTesting + static final int PHONE_BUCKET_SIZE_MILLIS = 60 * 60 * 1000; + /** Phone suggestions older than this value are considered too old. */ + @VisibleForTesting + static final long PHONE_MAX_AGE_MILLIS = PHONE_BUCKET_COUNT * PHONE_BUCKET_SIZE_MILLIS; + + @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL }) + @Retention(RetentionPolicy.SOURCE) + public @interface Origin {} + + /** Used when a time value originated from a telephony signal. */ + @Origin + private static final int ORIGIN_PHONE = 1; + + /** Used when a time value originated from a user / manual settings. */ + @Origin + private static final int ORIGIN_MANUAL = 2; + + /** + * CLOCK_PARANOIA: The maximum difference allowed between the expected system clock time and the + * actual system clock time before a warning is logged. Used to help identify situations where + * there is something other than this class setting the system clock. + */ + private static final long SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS = 2 * 1000; + + /** The number of previous phone suggestions to keep for each ID (for use during debugging). */ + private static final int KEEP_SUGGESTION_HISTORY_SIZE = 30; + + // A log for changes made to the system clock and why. + @NonNull + private final LocalLog mTimeChangesLog = new LocalLog(30, false /* useLocalTimestamps */); + + // @NonNull after initialize() + private Callback mCallback; + + // Used to store the last time the system clock state was set automatically. It is used to + // detect (and log) issues with the realtime clock or whether the clock is being set without + // going through this strategy code. + @GuardedBy("this") + @Nullable + private TimestampedValue<Long> mLastAutoSystemClockTimeSet; + + /** + * A mapping from phoneId to a linked list of time suggestions (the "first" being the latest). + * We typically expect one or two entries in this Map: devices will have a small number + * of telephony devices and phoneIds are assumed to be stable. The LinkedList associated with + * the ID will not exceed {@link #KEEP_SUGGESTION_HISTORY_SIZE} in size. + */ + @GuardedBy("this") + private ArrayMap<Integer, LinkedList<PhoneTimeSuggestion>> mSuggestionByPhoneId = + new ArrayMap<>(); + + @Override + public void initialize(@NonNull Callback callback) { + mCallback = callback; + } + + @Override + public synchronized void suggestManualTime(@NonNull ManualTimeSuggestion suggestion) { + final TimestampedValue<Long> newUtcTime = suggestion.getUtcTime(); + + if (!validateSuggestionTime(newUtcTime, suggestion)) { + return; + } + + String cause = "Manual time suggestion received: suggestion=" + suggestion; + setSystemClockIfRequired(ORIGIN_MANUAL, newUtcTime, cause); + } + + @Override + public synchronized void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) { + // Empty time suggestion means that telephony network connectivity has been lost. + // The passage of time is relentless, and we don't expect our users to use a time machine, + // so we can continue relying on previous suggestions when we lose connectivity. This is + // unlike time zone, where a user may lose connectivity when boarding a flight and where we + // do want to "forget" old signals. Suggestions that are too old are discarded later in the + // detection algorithm. + if (timeSuggestion.getUtcTime() == null) { + return; + } + + // Perform validation / input filtering and record the validated suggestion against the + // phoneId. + if (!validateAndStorePhoneSuggestion(timeSuggestion)) { + return; + } + + // Now perform auto time detection. The new suggestion may be used to modify the system + // clock. + String reason = "New phone time suggested. timeSuggestion=" + timeSuggestion; + doAutoTimeDetection(reason); + } + + @Override + public synchronized void handleAutoTimeDetectionChanged() { + boolean enabled = mCallback.isAutoTimeDetectionEnabled(); + // When automatic time detection is enabled we update the system clock instantly if we can. + // Conversely, when automatic time detection is disabled we leave the clock as it is. + if (enabled) { + String reason = "Auto time zone detection setting enabled."; + doAutoTimeDetection(reason); + } else { + // CLOCK_PARANOIA: We are losing "control" of the system clock so we cannot predict what + // it should be in future. + mLastAutoSystemClockTimeSet = null; + } + } + + @Override + public synchronized void dump(@NonNull PrintWriter pw, @Nullable String[] args) { + IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); + ipw.println("TimeDetectorStrategy:"); + ipw.increaseIndent(); // level 1 + + ipw.println("mLastAutoSystemClockTimeSet=" + mLastAutoSystemClockTimeSet); + + ipw.println("Time change log:"); + ipw.increaseIndent(); // level 2 + mTimeChangesLog.dump(ipw); + ipw.decreaseIndent(); // level 2 + + ipw.println("Phone suggestion history:"); + ipw.increaseIndent(); // level 2 + for (Map.Entry<Integer, LinkedList<PhoneTimeSuggestion>> entry + : mSuggestionByPhoneId.entrySet()) { + ipw.println("Phone " + entry.getKey()); + + ipw.increaseIndent(); // level 3 + for (PhoneTimeSuggestion suggestion : entry.getValue()) { + ipw.println(suggestion); + } + ipw.decreaseIndent(); // level 3 + } + ipw.decreaseIndent(); // level 2 + + ipw.decreaseIndent(); // level 1 + ipw.flush(); + } + + @GuardedBy("this") + private boolean validateAndStorePhoneSuggestion(@NonNull PhoneTimeSuggestion suggestion) { + TimestampedValue<Long> newUtcTime = suggestion.getUtcTime(); + if (!validateSuggestionTime(newUtcTime, suggestion)) { + // There's probably nothing useful we can do: elsewhere we assume that reference + // times are in the past so just stop here. + return false; + } + + int phoneId = suggestion.getPhoneId(); + LinkedList<PhoneTimeSuggestion> phoneSuggestions = mSuggestionByPhoneId.get(phoneId); + if (phoneSuggestions == null) { + // The first time we've seen this phoneId. + phoneSuggestions = new LinkedList<>(); + mSuggestionByPhoneId.put(phoneId, phoneSuggestions); + } else if (phoneSuggestions.isEmpty()) { + Slog.w(LOG_TAG, "Suggestions unexpectedly empty when adding suggestion=" + suggestion); + } + + if (!phoneSuggestions.isEmpty()) { + // We can log / discard suggestions with obvious issues with the reference time clock. + PhoneTimeSuggestion previousSuggestion = phoneSuggestions.getFirst(); + if (previousSuggestion == null + || previousSuggestion.getUtcTime() == null + || previousSuggestion.getUtcTime().getValue() == null) { + // This should be impossible given we only store validated suggestions. + Slog.w(LOG_TAG, "Previous suggestion is null or has a null time." + + " previousSuggestion=" + previousSuggestion + + ", suggestion=" + suggestion); + return false; + } + + long referenceTimeDifference = TimestampedValue.referenceTimeDifference( + newUtcTime, previousSuggestion.getUtcTime()); + if (referenceTimeDifference < 0) { + // The reference time is before the previously received suggestion. Ignore it. + Slog.w(LOG_TAG, "Out of order phone suggestion received." + + " referenceTimeDifference=" + referenceTimeDifference + + " previousSuggestion=" + previousSuggestion + + " suggestion=" + suggestion); + return false; + } + } + + // Store the latest suggestion. + phoneSuggestions.addFirst(suggestion); + if (phoneSuggestions.size() > KEEP_SUGGESTION_HISTORY_SIZE) { + phoneSuggestions.removeLast(); + } + return true; + } + + private boolean validateSuggestionTime( + @NonNull TimestampedValue<Long> newUtcTime, @NonNull Object suggestion) { + if (newUtcTime.getValue() == null) { + Slog.w(LOG_TAG, "Suggested time value is null. suggestion=" + suggestion); + return false; + } + + // We can validate the suggestion against the reference time clock. + long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis(); + if (elapsedRealtimeMillis < newUtcTime.getReferenceTimeMillis()) { + // elapsedRealtime clock went backwards? + Slog.w(LOG_TAG, "New reference time is in the future? Ignoring." + + " elapsedRealtimeMillis=" + elapsedRealtimeMillis + + ", suggestion=" + suggestion); + return false; + } + return true; + } + + @GuardedBy("this") + private void doAutoTimeDetection(@NonNull String detectionReason) { + if (!mCallback.isAutoTimeDetectionEnabled()) { + // Avoid doing unnecessary work with this (race-prone) check. + return; + } + + PhoneTimeSuggestion bestPhoneSuggestion = findBestPhoneSuggestion(); + + // Work out what to do with the best suggestion. + if (bestPhoneSuggestion == null) { + // There is no good phone suggestion. + if (DBG) { + Slog.d(LOG_TAG, "Could not determine time: No best phone suggestion." + + " detectionReason=" + detectionReason); + } + return; + } + + final TimestampedValue<Long> newUtcTime = bestPhoneSuggestion.getUtcTime(); + String cause = "Found good suggestion." + + ", bestPhoneSuggestion=" + bestPhoneSuggestion + + ", detectionReason=" + detectionReason; + setSystemClockIfRequired(ORIGIN_PHONE, newUtcTime, cause); + } + + @GuardedBy("this") + @Nullable + private PhoneTimeSuggestion findBestPhoneSuggestion() { + long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis(); + + // Phone time suggestions are assumed to be derived from NITZ or NITZ-like signals. These + // have a number of limitations: + // 1) No guarantee of accuracy ("accuracy of the time information is in the order of + // minutes") [1] + // 2) No guarantee of regular signals ("dependent on the handset crossing radio network + // boundaries") [1] + // + // [1] https://en.wikipedia.org/wiki/NITZ + // + // Generally, when there are suggestions from multiple phoneIds they should usually + // approximately agree. In cases where signals *are* inaccurate we don't want to vacillate + // between signals from two phoneIds. However, it is known for NITZ signals to be incorrect + // occasionally, which means we also don't want to stick forever with one phoneId. Without + // cross-referencing across sources (e.g. the current device time, NTP), or doing some kind + // of statistical analysis of consistency within and across phoneIds, we can't know which + // suggestions are more correct. + // + // For simplicity, we try to value recency, then consistency of phoneId. + // + // The heuristic works as follows: + // Recency: The most recent suggestion from each phone is scored. The score is based on a + // discrete age bucket, i.e. so signals received around the same time will be in the same + // bucket, thus applying a loose reference time ordering. The suggestion with the highest + // score is used. + // Consistency: If there a multiple suggestions with the same score, the suggestion with the + // lowest phoneId is always taken. + // + // In the trivial case with a single ID this will just mean that the latest received + // suggestion is used. + + PhoneTimeSuggestion bestSuggestion = null; + int bestScore = PHONE_INVALID_SCORE; + for (int i = 0; i < mSuggestionByPhoneId.size(); i++) { + Integer phoneId = mSuggestionByPhoneId.keyAt(i); + LinkedList<PhoneTimeSuggestion> phoneSuggestions = mSuggestionByPhoneId.valueAt(i); + if (phoneSuggestions == null) { + // Unexpected - map is missing a value. + Slog.w(LOG_TAG, "Suggestions unexpectedly missing for phoneId." + + " phoneId=" + phoneId); + continue; + } + + PhoneTimeSuggestion candidateSuggestion = phoneSuggestions.getFirst(); + if (candidateSuggestion == null) { + // Unexpected - null suggestions should never be stored. + Slog.w(LOG_TAG, "Latest suggestion unexpectedly null for phoneId." + + " phoneId=" + phoneId); + continue; + } else if (candidateSuggestion.getUtcTime() == null) { + // Unexpected - we do not store empty suggestions. + Slog.w(LOG_TAG, "Latest suggestion unexpectedly empty. " + + " candidateSuggestion=" + candidateSuggestion); + continue; + } + + int candidateScore = scorePhoneSuggestion(elapsedRealtimeMillis, candidateSuggestion); + if (candidateScore == PHONE_INVALID_SCORE) { + // Expected: This means the suggestion is obviously invalid or just too old. + continue; + } + + // Higher scores are better. + if (bestSuggestion == null || bestScore < candidateScore) { + bestSuggestion = candidateSuggestion; + bestScore = candidateScore; + } else if (bestScore == candidateScore) { + // Tie! Use the suggestion with the lowest phoneId. + int candidatePhoneId = candidateSuggestion.getPhoneId(); + int bestPhoneId = bestSuggestion.getPhoneId(); + if (candidatePhoneId < bestPhoneId) { + bestSuggestion = candidateSuggestion; + } + } + } + return bestSuggestion; + } + + private static int scorePhoneSuggestion( + long elapsedRealtimeMillis, @NonNull PhoneTimeSuggestion timeSuggestion) { + // The score is based on the age since receipt. Suggestions are bucketed so two + // suggestions in the same bucket from different phoneIds are scored the same. + TimestampedValue<Long> utcTime = timeSuggestion.getUtcTime(); + long referenceTimeMillis = utcTime.getReferenceTimeMillis(); + if (referenceTimeMillis > elapsedRealtimeMillis) { + // Future times are ignored. They imply the reference time was wrong, or the elapsed + // realtime clock has gone backwards, neither of which are supportable situations. + Slog.w(LOG_TAG, "Existing suggestion found to be in the future. " + + " elapsedRealtimeMillis=" + elapsedRealtimeMillis + + ", timeSuggestion=" + timeSuggestion); + return PHONE_INVALID_SCORE; + } + + long ageMillis = elapsedRealtimeMillis - referenceTimeMillis; + + // Any suggestion > MAX_AGE_MILLIS is treated as too old. Although time is relentless and + // predictable, the accuracy of the reference time clock may be poor over long periods which + // would lead to errors creeping in. Also, in edge cases where a bad suggestion has been + // made and never replaced, it could also mean that the time detection code remains + // opinionated using a bad invalid suggestion. This caps that edge case at MAX_AGE_MILLIS. + if (ageMillis > PHONE_MAX_AGE_MILLIS) { + return PHONE_INVALID_SCORE; + } + + // Turn the age into a discrete value: 0 <= bucketIndex < MAX_AGE_HOURS. + int bucketIndex = (int) (ageMillis / PHONE_BUCKET_SIZE_MILLIS); + + // We want the lowest bucket index to have the highest score. 0 > score >= BUCKET_COUNT. + return PHONE_BUCKET_COUNT - bucketIndex; + } + + @GuardedBy("this") + private void setSystemClockIfRequired( + @Origin int origin, @NonNull TimestampedValue<Long> time, @NonNull String cause) { + + boolean isOriginAutomatic = isOriginAutomatic(origin); + if (isOriginAutomatic) { + if (!mCallback.isAutoTimeDetectionEnabled()) { + if (DBG) { + Slog.d(LOG_TAG, "Auto time detection is not enabled." + + " origin=" + origin + + ", time=" + time + + ", cause=" + cause); + } + return; + } + } else { + if (mCallback.isAutoTimeDetectionEnabled()) { + if (DBG) { + Slog.d(LOG_TAG, "Auto time detection is enabled." + + " origin=" + origin + + ", time=" + time + + ", cause=" + cause); + } + return; + } + } + + mCallback.acquireWakeLock(); + try { + setSystemClockUnderWakeLock(origin, time, cause); + } finally { + mCallback.releaseWakeLock(); + } + } + + private static boolean isOriginAutomatic(@Origin int origin) { + return origin == ORIGIN_PHONE; + } + + @GuardedBy("this") + private void setSystemClockUnderWakeLock( + int origin, @NonNull TimestampedValue<Long> newTime, @NonNull Object cause) { + + long elapsedRealtimeMillis = mCallback.elapsedRealtimeMillis(); + boolean isOriginAutomatic = isOriginAutomatic(origin); + long actualSystemClockMillis = mCallback.systemClockMillis(); + if (isOriginAutomatic) { + // CLOCK_PARANOIA : Check to see if this class owns the clock or if something else + // may be setting the clock. + if (mLastAutoSystemClockTimeSet != null) { + long expectedTimeMillis = TimeDetectorStrategy.getTimeAt( + mLastAutoSystemClockTimeSet, elapsedRealtimeMillis); + long absSystemClockDifference = + Math.abs(expectedTimeMillis - actualSystemClockMillis); + if (absSystemClockDifference > SYSTEM_CLOCK_PARANOIA_THRESHOLD_MILLIS) { + Slog.w(LOG_TAG, + "System clock has not tracked elapsed real time clock. A clock may" + + " be inaccurate or something unexpectedly set the system" + + " clock." + + " elapsedRealtimeMillis=" + elapsedRealtimeMillis + + " expectedTimeMillis=" + expectedTimeMillis + + " actualTimeMillis=" + actualSystemClockMillis + + " cause=" + cause); + } + } + } + + // Adjust for the time that has elapsed since the signal was received. + long newSystemClockMillis = TimeDetectorStrategy.getTimeAt(newTime, elapsedRealtimeMillis); + + // Check if the new signal would make sufficient difference to the system clock. If it's + // below the threshold then ignore it. + long absTimeDifference = Math.abs(newSystemClockMillis - actualSystemClockMillis); + long systemClockUpdateThreshold = mCallback.systemClockUpdateThresholdMillis(); + if (absTimeDifference < systemClockUpdateThreshold) { + if (DBG) { + Slog.d(LOG_TAG, "Not setting system clock. New time and" + + " system clock are close enough." + + " elapsedRealtimeMillis=" + elapsedRealtimeMillis + + " newTime=" + newTime + + " cause=" + cause + + " systemClockUpdateThreshold=" + systemClockUpdateThreshold + + " absTimeDifference=" + absTimeDifference); + } + return; + } + + mCallback.setSystemClock(newSystemClockMillis); + String logMsg = "Set system clock using time=" + newTime + + " cause=" + cause + + " elapsedRealtimeMillis=" + elapsedRealtimeMillis + + " newSystemClockMillis=" + newSystemClockMillis; + if (DBG) { + Slog.d(LOG_TAG, logMsg); + } + mTimeChangesLog.log(logMsg); + + // CLOCK_PARANOIA : Record the last time this class set the system clock due to an auto-time + // signal, or clear the record it is being done manually. + if (isOriginAutomatic(origin)) { + mLastAutoSystemClockTimeSet = newTime; + } else { + mLastAutoSystemClockTimeSet = null; + } + + // Historically, Android has sent a TelephonyIntents.ACTION_NETWORK_SET_TIME broadcast only + // when setting the time using NITZ. + if (origin == ORIGIN_PHONE) { + // Send a broadcast that telephony code used to send after setting the clock. + // TODO Remove this broadcast as soon as there are no remaining listeners. + Intent intent = new Intent(TelephonyIntents.ACTION_NETWORK_SET_TIME); + intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); + intent.putExtra("time", newSystemClockMillis); + mCallback.sendStickyBroadcast(intent); + } + } + + /** + * Returns the current best phone suggestion. Not intended for general use: it is used during + * tests to check strategy behavior. + */ + @VisibleForTesting + @Nullable + public synchronized PhoneTimeSuggestion findBestPhoneSuggestionForTests() { + return findBestPhoneSuggestion(); + } + + /** + * A method used to inspect state during tests. Not intended for general use. + */ + @VisibleForTesting + @Nullable + public synchronized PhoneTimeSuggestion getLatestPhoneSuggestion(int phoneId) { + LinkedList<PhoneTimeSuggestion> suggestions = mSuggestionByPhoneId.get(phoneId); + if (suggestions == null) { + return null; + } + return suggestions.getFirst(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeDetectorStrategyTest.java deleted file mode 100644 index 7a0a28dfbd16..000000000000 --- a/services/tests/servicestests/src/com/android/server/timedetector/SimpleTimeDetectorStrategyTest.java +++ /dev/null @@ -1,661 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.timedetector; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import android.app.timedetector.ManualTimeSuggestion; -import android.app.timedetector.PhoneTimeSuggestion; -import android.content.Intent; -import android.icu.util.Calendar; -import android.icu.util.GregorianCalendar; -import android.icu.util.TimeZone; -import android.util.TimestampedValue; - -import androidx.test.runner.AndroidJUnit4; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.time.Duration; - -@RunWith(AndroidJUnit4.class) -public class SimpleTimeDetectorStrategyTest { - - private static final Scenario SCENARIO_1 = new Scenario.Builder() - .setInitialDeviceSystemClockUtc(1977, 1, 1, 12, 0, 0) - .setInitialDeviceRealtimeMillis(123456789L) - .setActualTimeUtc(2018, 1, 1, 12, 0, 0) - .build(); - - private static final int ARBITRARY_PHONE_ID = 123456; - - private static final long ONE_DAY_MILLIS = Duration.ofDays(1).toMillis(); - - private Script mScript; - - @Before - public void setUp() { - mScript = new Script(); - } - - @Test - public void testSuggestPhoneTime_autoTimeEnabled() { - Scenario scenario = SCENARIO_1; - mScript.pokeFakeClocks(scenario) - .pokeTimeDetectionEnabled(true); - - PhoneTimeSuggestion timeSuggestion = - scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID); - final int clockIncrement = 1000; - long expectSystemClockMillis = scenario.getActualTimeMillis() + clockIncrement; - - mScript.simulateTimePassing(clockIncrement) - .simulatePhoneTimeSuggestion(timeSuggestion) - .verifySystemClockWasSetAndResetCallTracking( - expectSystemClockMillis, true /* expectNetworkBroadcast */); - } - - @Test - public void testSuggestPhoneTime_emptySuggestionIgnored() { - Scenario scenario = SCENARIO_1; - mScript.pokeFakeClocks(scenario) - .pokeTimeDetectionEnabled(true); - - PhoneTimeSuggestion timeSuggestion = createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, null); - - mScript.simulatePhoneTimeSuggestion(timeSuggestion) - .verifySystemClockWasNotSetAndResetCallTracking(); - } - - @Test - public void testSuggestPhoneTime_systemClockThreshold() { - Scenario scenario = SCENARIO_1; - final int systemClockUpdateThresholdMillis = 1000; - mScript.pokeFakeClocks(scenario) - .pokeThresholds(systemClockUpdateThresholdMillis) - .pokeTimeDetectionEnabled(true); - - PhoneTimeSuggestion timeSuggestion1 = - scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID); - TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime(); - - final int clockIncrement = 100; - // Increment the the device clocks to simulate the passage of time. - mScript.simulateTimePassing(clockIncrement); - - long expectSystemClockMillis1 = - TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis()); - - // Send the first time signal. It should be used. - mScript.simulatePhoneTimeSuggestion(timeSuggestion1) - .verifySystemClockWasSetAndResetCallTracking( - expectSystemClockMillis1, true /* expectNetworkBroadcast */); - - // Now send another time signal, but one that is too similar to the last one and should be - // ignored. - int underThresholdMillis = systemClockUpdateThresholdMillis - 1; - TimestampedValue<Long> utcTime2 = new TimestampedValue<>( - mScript.peekElapsedRealtimeMillis(), - mScript.peekSystemClockMillis() + underThresholdMillis); - PhoneTimeSuggestion timeSuggestion2 = - createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime2); - mScript.simulateTimePassing(clockIncrement) - .simulatePhoneTimeSuggestion(timeSuggestion2) - .verifySystemClockWasNotSetAndResetCallTracking(); - - // Now send another time signal, but one that is on the threshold and so should be used. - TimestampedValue<Long> utcTime3 = new TimestampedValue<>( - mScript.peekElapsedRealtimeMillis(), - mScript.peekSystemClockMillis() + systemClockUpdateThresholdMillis); - - PhoneTimeSuggestion timeSuggestion3 = - createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime3); - mScript.simulateTimePassing(clockIncrement); - - long expectSystemClockMillis3 = - TimeDetectorStrategy.getTimeAt(utcTime3, mScript.peekElapsedRealtimeMillis()); - - mScript.simulatePhoneTimeSuggestion(timeSuggestion3) - .verifySystemClockWasSetAndResetCallTracking( - expectSystemClockMillis3, true /* expectNetworkBroadcast */); - } - - @Test - public void testSuggestPhoneTime_autoTimeDisabled() { - Scenario scenario = SCENARIO_1; - mScript.pokeFakeClocks(scenario) - .pokeTimeDetectionEnabled(false); - - PhoneTimeSuggestion timeSuggestion = - scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID); - mScript.simulatePhoneTimeSuggestion(timeSuggestion) - .verifySystemClockWasNotSetAndResetCallTracking(); - } - - @Test - public void testSuggestPhoneTime_invalidNitzReferenceTimesIgnored() { - Scenario scenario = SCENARIO_1; - final int systemClockUpdateThreshold = 2000; - mScript.pokeFakeClocks(scenario) - .pokeThresholds(systemClockUpdateThreshold) - .pokeTimeDetectionEnabled(true); - PhoneTimeSuggestion timeSuggestion1 = - scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID); - TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime(); - - // Initialize the strategy / device with a time set from NITZ. - mScript.simulateTimePassing(100); - long expectedSystemClockMillis1 = - TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis()); - mScript.simulatePhoneTimeSuggestion(timeSuggestion1) - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis1, true /* expectNetworkBroadcast */); - - // The UTC time increment should be larger than the system clock update threshold so we - // know it shouldn't be ignored for other reasons. - long validUtcTimeMillis = utcTime1.getValue() + (2 * systemClockUpdateThreshold); - - // Now supply a new signal that has an obviously bogus reference time : older than the last - // one. - long referenceTimeBeforeLastSignalMillis = utcTime1.getReferenceTimeMillis() - 1; - TimestampedValue<Long> utcTime2 = new TimestampedValue<>( - referenceTimeBeforeLastSignalMillis, validUtcTimeMillis); - PhoneTimeSuggestion timeSuggestion2 = - createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime2); - mScript.simulatePhoneTimeSuggestion(timeSuggestion2) - .verifySystemClockWasNotSetAndResetCallTracking(); - - // Now supply a new signal that has an obviously bogus reference time : substantially in the - // future. - long referenceTimeInFutureMillis = - utcTime1.getReferenceTimeMillis() + Integer.MAX_VALUE + 1; - TimestampedValue<Long> utcTime3 = new TimestampedValue<>( - referenceTimeInFutureMillis, validUtcTimeMillis); - PhoneTimeSuggestion timeSuggestion3 = - createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime3); - mScript.simulatePhoneTimeSuggestion(timeSuggestion3) - .verifySystemClockWasNotSetAndResetCallTracking(); - - // Just to prove validUtcTimeMillis is valid. - long validReferenceTimeMillis = utcTime1.getReferenceTimeMillis() + 100; - TimestampedValue<Long> utcTime4 = new TimestampedValue<>( - validReferenceTimeMillis, validUtcTimeMillis); - long expectedSystemClockMillis4 = - TimeDetectorStrategy.getTimeAt(utcTime4, mScript.peekElapsedRealtimeMillis()); - PhoneTimeSuggestion timeSuggestion4 = - createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime4); - mScript.simulatePhoneTimeSuggestion(timeSuggestion4) - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis4, true /* expectNetworkBroadcast */); - } - - @Test - public void testSuggestPhoneTime_timeDetectionToggled() { - Scenario scenario = SCENARIO_1; - final int clockIncrementMillis = 100; - final int systemClockUpdateThreshold = 2000; - mScript.pokeFakeClocks(scenario) - .pokeThresholds(systemClockUpdateThreshold) - .pokeTimeDetectionEnabled(false); - - PhoneTimeSuggestion timeSuggestion1 = - scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID); - TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime(); - - // Simulate time passing. - mScript.simulateTimePassing(clockIncrementMillis); - - // Simulate the time signal being received. It should not be used because auto time - // detection is off but it should be recorded. - mScript.simulatePhoneTimeSuggestion(timeSuggestion1) - .verifySystemClockWasNotSetAndResetCallTracking(); - - // Simulate more time passing. - mScript.simulateTimePassing(clockIncrementMillis); - - long expectedSystemClockMillis1 = - TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis()); - - // Turn on auto time detection. - mScript.simulateAutoTimeDetectionToggle() - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis1, true /* expectNetworkBroadcast */); - - // Turn off auto time detection. - mScript.simulateAutoTimeDetectionToggle() - .verifySystemClockWasNotSetAndResetCallTracking(); - - // Receive another valid time signal. - // It should be on the threshold and accounting for the clock increments. - TimestampedValue<Long> utcTime2 = new TimestampedValue<>( - mScript.peekElapsedRealtimeMillis(), - mScript.peekSystemClockMillis() + systemClockUpdateThreshold); - PhoneTimeSuggestion timeSuggestion2 = - createPhoneTimeSuggestion(ARBITRARY_PHONE_ID, utcTime2); - - // Simulate more time passing. - mScript.simulateTimePassing(clockIncrementMillis); - - long expectedSystemClockMillis2 = - TimeDetectorStrategy.getTimeAt(utcTime2, mScript.peekElapsedRealtimeMillis()); - - // The new time, though valid, should not be set in the system clock because auto time is - // disabled. - mScript.simulatePhoneTimeSuggestion(timeSuggestion2) - .verifySystemClockWasNotSetAndResetCallTracking(); - - // Turn on auto time detection. - mScript.simulateAutoTimeDetectionToggle() - .verifySystemClockWasSetAndResetCallTracking( - expectedSystemClockMillis2, true /* expectNetworkBroadcast */); - } - - @Test - public void testSuggestManualTime_autoTimeDisabled() { - Scenario scenario = SCENARIO_1; - mScript.pokeFakeClocks(scenario) - .pokeTimeDetectionEnabled(false); - - ManualTimeSuggestion timeSuggestion = scenario.createManualTimeSuggestionForActual(); - final int clockIncrement = 1000; - long expectSystemClockMillis = scenario.getActualTimeMillis() + clockIncrement; - - mScript.simulateTimePassing(clockIncrement) - .simulateManualTimeSuggestion(timeSuggestion) - .verifySystemClockWasSetAndResetCallTracking( - expectSystemClockMillis, false /* expectNetworkBroadcast */); - } - - @Test - public void testSuggestManualTime_retainsAutoSignal() { - Scenario scenario = SCENARIO_1; - - // Configure the start state. - mScript.pokeFakeClocks(scenario) - .pokeTimeDetectionEnabled(true); - - // Simulate a phone suggestion. - PhoneTimeSuggestion phoneTimeSuggestion = - scenario.createPhoneTimeSuggestionForActual(ARBITRARY_PHONE_ID); - long expectedAutoClockMillis = phoneTimeSuggestion.getUtcTime().getValue(); - final int clockIncrement = 1000; - - // Simulate the passage of time. - mScript.simulateTimePassing(clockIncrement); - expectedAutoClockMillis += clockIncrement; - - mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion) - .verifySystemClockWasSetAndResetCallTracking( - expectedAutoClockMillis, true /* expectNetworkBroadcast */); - - // Simulate the passage of time. - mScript.simulateTimePassing(clockIncrement); - expectedAutoClockMillis += clockIncrement; - - // Switch to manual. - mScript.simulateAutoTimeDetectionToggle() - .verifySystemClockWasNotSetAndResetCallTracking(); - - // Simulate the passage of time. - mScript.simulateTimePassing(clockIncrement); - expectedAutoClockMillis += clockIncrement; - - - // Simulate a manual suggestion 1 day different from the auto suggestion. - long manualTimeMillis = SCENARIO_1.getActualTimeMillis() + ONE_DAY_MILLIS; - long expectedManualClockMillis = manualTimeMillis; - ManualTimeSuggestion manualTimeSuggestion = createManualTimeSuggestion(manualTimeMillis); - mScript.simulateManualTimeSuggestion(manualTimeSuggestion) - .verifySystemClockWasSetAndResetCallTracking( - expectedManualClockMillis, false /* expectNetworkBroadcast */); - - // Simulate the passage of time. - mScript.simulateTimePassing(clockIncrement); - expectedAutoClockMillis += clockIncrement; - - // Switch back to auto. - mScript.simulateAutoTimeDetectionToggle(); - - mScript.verifySystemClockWasSetAndResetCallTracking( - expectedAutoClockMillis, true /* expectNetworkBroadcast */); - - // Switch back to manual - nothing should happen to the clock. - mScript.simulateAutoTimeDetectionToggle() - .verifySystemClockWasNotSetAndResetCallTracking(); - } - - /** - * Manual suggestions should be ignored if auto time is enabled. - */ - @Test - public void testSuggestManualTime_autoTimeEnabled() { - Scenario scenario = SCENARIO_1; - mScript.pokeFakeClocks(scenario) - .pokeTimeDetectionEnabled(true); - - ManualTimeSuggestion timeSuggestion = scenario.createManualTimeSuggestionForActual(); - final int clockIncrement = 1000; - - mScript.simulateTimePassing(clockIncrement) - .simulateManualTimeSuggestion(timeSuggestion) - .verifySystemClockWasNotSetAndResetCallTracking(); - } - - /** - * A fake implementation of TimeDetectorStrategy.Callback. Besides tracking changes and behaving - * like the real thing should, it also asserts preconditions. - */ - private static class FakeCallback implements TimeDetectorStrategy.Callback { - private boolean mTimeDetectionEnabled; - private boolean mWakeLockAcquired; - private long mElapsedRealtimeMillis; - private long mSystemClockMillis; - private int mSystemClockUpdateThresholdMillis = 2000; - - // Tracking operations. - private boolean mSystemClockWasSet; - private Intent mBroadcastSent; - - @Override - public int systemClockUpdateThresholdMillis() { - return mSystemClockUpdateThresholdMillis; - } - - @Override - public boolean isAutoTimeDetectionEnabled() { - return mTimeDetectionEnabled; - } - - @Override - public void acquireWakeLock() { - if (mWakeLockAcquired) { - fail("Wake lock already acquired"); - } - mWakeLockAcquired = true; - } - - @Override - public long elapsedRealtimeMillis() { - assertWakeLockAcquired(); - return mElapsedRealtimeMillis; - } - - @Override - public long systemClockMillis() { - assertWakeLockAcquired(); - return mSystemClockMillis; - } - - @Override - public void setSystemClock(long newTimeMillis) { - assertWakeLockAcquired(); - mSystemClockWasSet = true; - mSystemClockMillis = newTimeMillis; - } - - @Override - public void releaseWakeLock() { - assertWakeLockAcquired(); - mWakeLockAcquired = false; - } - - @Override - public void sendStickyBroadcast(Intent intent) { - assertNotNull(intent); - mBroadcastSent = intent; - } - - // Methods below are for managing the fake's behavior. - - public void pokeSystemClockUpdateThreshold(int thresholdMillis) { - mSystemClockUpdateThresholdMillis = thresholdMillis; - } - - public void pokeElapsedRealtimeMillis(long elapsedRealtimeMillis) { - mElapsedRealtimeMillis = elapsedRealtimeMillis; - } - - public void pokeSystemClockMillis(long systemClockMillis) { - mSystemClockMillis = systemClockMillis; - } - - public void pokeAutoTimeDetectionEnabled(boolean enabled) { - mTimeDetectionEnabled = enabled; - } - - public long peekElapsedRealtimeMillis() { - return mElapsedRealtimeMillis; - } - - public long peekSystemClockMillis() { - return mSystemClockMillis; - } - - public void simulateTimePassing(int incrementMillis) { - mElapsedRealtimeMillis += incrementMillis; - mSystemClockMillis += incrementMillis; - } - - public void simulateAutoTimeZoneDetectionToggle() { - mTimeDetectionEnabled = !mTimeDetectionEnabled; - } - - public void verifySystemClockNotSet() { - assertFalse(mSystemClockWasSet); - } - - public void verifySystemClockWasSet(long expectSystemClockMillis) { - assertTrue(mSystemClockWasSet); - assertEquals(expectSystemClockMillis, mSystemClockMillis); - } - - public void verifyIntentWasBroadcast() { - assertTrue(mBroadcastSent != null); - } - - public void verifyIntentWasNotBroadcast() { - assertNull(mBroadcastSent); - } - - public void resetCallTracking() { - mSystemClockWasSet = false; - mBroadcastSent = null; - } - - private void assertWakeLockAcquired() { - assertTrue("The operation must be performed only after acquiring the wakelock", - mWakeLockAcquired); - } - } - - /** - * A fluent helper class for tests. - */ - private class Script { - - private final FakeCallback mFakeCallback; - private final SimpleTimeDetectorStrategy mSimpleTimeDetectorStrategy; - - Script() { - mFakeCallback = new FakeCallback(); - mSimpleTimeDetectorStrategy = new SimpleTimeDetectorStrategy(); - mSimpleTimeDetectorStrategy.initialize(mFakeCallback); - - } - - Script pokeTimeDetectionEnabled(boolean enabled) { - mFakeCallback.pokeAutoTimeDetectionEnabled(enabled); - return this; - } - - Script pokeFakeClocks(Scenario scenario) { - mFakeCallback.pokeElapsedRealtimeMillis(scenario.getInitialRealTimeMillis()); - mFakeCallback.pokeSystemClockMillis(scenario.getInitialSystemClockMillis()); - return this; - } - - Script pokeThresholds(int systemClockUpdateThreshold) { - mFakeCallback.pokeSystemClockUpdateThreshold(systemClockUpdateThreshold); - return this; - } - - long peekElapsedRealtimeMillis() { - return mFakeCallback.peekElapsedRealtimeMillis(); - } - - long peekSystemClockMillis() { - return mFakeCallback.peekSystemClockMillis(); - } - - Script simulatePhoneTimeSuggestion(PhoneTimeSuggestion timeSuggestion) { - mSimpleTimeDetectorStrategy.suggestPhoneTime(timeSuggestion); - return this; - } - - Script simulateManualTimeSuggestion(ManualTimeSuggestion timeSuggestion) { - mSimpleTimeDetectorStrategy.suggestManualTime(timeSuggestion); - return this; - } - - Script simulateAutoTimeDetectionToggle() { - mFakeCallback.simulateAutoTimeZoneDetectionToggle(); - mSimpleTimeDetectorStrategy.handleAutoTimeDetectionChanged(); - return this; - } - - Script simulateTimePassing(int clockIncrement) { - mFakeCallback.simulateTimePassing(clockIncrement); - return this; - } - - Script verifySystemClockWasNotSetAndResetCallTracking() { - mFakeCallback.verifySystemClockNotSet(); - mFakeCallback.verifyIntentWasNotBroadcast(); - mFakeCallback.resetCallTracking(); - return this; - } - - Script verifySystemClockWasSetAndResetCallTracking( - long expectSystemClockMillis, boolean expectNetworkBroadcast) { - mFakeCallback.verifySystemClockWasSet(expectSystemClockMillis); - if (expectNetworkBroadcast) { - mFakeCallback.verifyIntentWasBroadcast(); - } - mFakeCallback.resetCallTracking(); - return this; - } - } - - /** - * A starting scenario used during tests. Describes a fictional "physical" reality. - */ - private static class Scenario { - - private final long mInitialDeviceSystemClockMillis; - private final long mInitialDeviceRealtimeMillis; - private final long mActualTimeMillis; - - Scenario(long initialDeviceSystemClock, long elapsedRealtime, long timeMillis) { - mInitialDeviceSystemClockMillis = initialDeviceSystemClock; - mActualTimeMillis = timeMillis; - mInitialDeviceRealtimeMillis = elapsedRealtime; - } - - long getInitialRealTimeMillis() { - return mInitialDeviceRealtimeMillis; - } - - long getInitialSystemClockMillis() { - return mInitialDeviceSystemClockMillis; - } - - long getActualTimeMillis() { - return mActualTimeMillis; - } - - PhoneTimeSuggestion createPhoneTimeSuggestionForActual(int phoneId) { - TimestampedValue<Long> time = new TimestampedValue<>( - mInitialDeviceRealtimeMillis, mActualTimeMillis); - return createPhoneTimeSuggestion(phoneId, time); - } - - ManualTimeSuggestion createManualTimeSuggestionForActual() { - TimestampedValue<Long> time = new TimestampedValue<>( - mInitialDeviceRealtimeMillis, mActualTimeMillis); - return new ManualTimeSuggestion(time); - } - - static class Builder { - - private long mInitialDeviceSystemClockMillis; - private long mInitialDeviceRealtimeMillis; - private long mActualTimeMillis; - - Builder setInitialDeviceSystemClockUtc(int year, int monthInYear, int day, - int hourOfDay, int minute, int second) { - mInitialDeviceSystemClockMillis = createUtcTime(year, monthInYear, day, hourOfDay, - minute, second); - return this; - } - - Builder setInitialDeviceRealtimeMillis(long realtimeMillis) { - mInitialDeviceRealtimeMillis = realtimeMillis; - return this; - } - - Builder setActualTimeUtc(int year, int monthInYear, int day, int hourOfDay, - int minute, int second) { - mActualTimeMillis = - createUtcTime(year, monthInYear, day, hourOfDay, minute, second); - return this; - } - - Scenario build() { - return new Scenario(mInitialDeviceSystemClockMillis, mInitialDeviceRealtimeMillis, - mActualTimeMillis); - } - } - } - - private static PhoneTimeSuggestion createPhoneTimeSuggestion(int phoneId, - TimestampedValue<Long> utcTime) { - return new PhoneTimeSuggestion.Builder(phoneId) - .setUtcTime(utcTime) - .build(); - } - - private ManualTimeSuggestion createManualTimeSuggestion(long timeMillis) { - TimestampedValue<Long> utcTime = - new TimestampedValue<>(mScript.peekElapsedRealtimeMillis(), timeMillis); - return new ManualTimeSuggestion(utcTime); - } - - private static long createUtcTime(int year, int monthInYear, int day, int hourOfDay, int minute, - int second) { - Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC")); - cal.clear(); - cal.set(year, monthInYear - 1, day, hourOfDay, minute, second); - return cal.getTimeInMillis(); - } -} diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java index 84b495f14c1f..72a7f508772b 100644 --- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java @@ -38,8 +38,6 @@ import android.util.TimestampedValue; import androidx.test.runner.AndroidJUnit4; -import com.android.server.timedetector.TimeDetectorStrategy.Callback; - import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -52,7 +50,6 @@ public class TimeDetectorServiceTest { private Context mMockContext; private StubbedTimeDetectorStrategy mStubbedTimeDetectorStrategy; - private Callback mMockCallback; private TimeDetectorService mTimeDetectorService; private HandlerThread mHandlerThread; @@ -68,12 +65,10 @@ public class TimeDetectorServiceTest { mHandlerThread.start(); mTestHandler = new TestHandler(mHandlerThread.getLooper()); - mMockCallback = mock(Callback.class); mStubbedTimeDetectorStrategy = new StubbedTimeDetectorStrategy(); mTimeDetectorService = new TimeDetectorService( - mMockContext, mTestHandler, mMockCallback, - mStubbedTimeDetectorStrategy); + mMockContext, mTestHandler, mStubbedTimeDetectorStrategy); } @After @@ -100,13 +95,13 @@ public class TimeDetectorServiceTest { @Test public void testSuggestManualTime() throws Exception { - doNothing().when(mMockContext).enforceCallingPermission(anyString(), any()); + doNothing().when(mMockContext).enforceCallingOrSelfPermission(anyString(), any()); ManualTimeSuggestion manualTimeSuggestion = createManualTimeSuggestion(); mTimeDetectorService.suggestManualTime(manualTimeSuggestion); mTestHandler.assertTotalMessagesEnqueued(1); - verify(mMockContext).enforceCallingPermission( + verify(mMockContext).enforceCallingOrSelfPermission( eq(android.Manifest.permission.SET_TIME), anyString()); diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java new file mode 100644 index 000000000000..1aa3d8fe654b --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java @@ -0,0 +1,758 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.timedetector; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import android.app.timedetector.ManualTimeSuggestion; +import android.app.timedetector.PhoneTimeSuggestion; +import android.content.Intent; +import android.icu.util.Calendar; +import android.icu.util.GregorianCalendar; +import android.icu.util.TimeZone; +import android.util.TimestampedValue; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.time.Duration; + +@RunWith(AndroidJUnit4.class) +public class TimeDetectorStrategyImplTest { + + private static final TimestampedValue<Long> ARBITRARY_CLOCK_INITIALIZATION_INFO = + new TimestampedValue<>( + 123456789L /* realtimeClockMillis */, + createUtcTime(1977, 1, 1, 12, 0, 0)); + + private static final long ARBITRARY_TEST_TIME_MILLIS = createUtcTime(2018, 1, 1, 12, 0, 0); + + private static final int ARBITRARY_PHONE_ID = 123456; + + private static final long ONE_DAY_MILLIS = Duration.ofDays(1).toMillis(); + + private Script mScript; + + @Before + public void setUp() { + mScript = new Script(); + } + + @Test + public void testSuggestPhoneTime_autoTimeEnabled() { + mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + .pokeAutoTimeDetectionEnabled(true); + + int phoneId = ARBITRARY_PHONE_ID; + long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; + PhoneTimeSuggestion timeSuggestion = + mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); + int clockIncrement = 1000; + long expectedSystemClockMillis = testTimeMillis + clockIncrement; + + mScript.simulateTimePassing(clockIncrement) + .simulatePhoneTimeSuggestion(timeSuggestion) + .verifySystemClockWasSetAndResetCallTracking( + expectedSystemClockMillis, true /* expectNetworkBroadcast */) + .assertLatestPhoneSuggestion(phoneId, timeSuggestion); + } + + @Test + public void testSuggestPhoneTime_emptySuggestionIgnored() { + mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + .pokeAutoTimeDetectionEnabled(true); + + int phoneId = ARBITRARY_PHONE_ID; + PhoneTimeSuggestion timeSuggestion = + mScript.generatePhoneTimeSuggestion(phoneId, null); + mScript.simulatePhoneTimeSuggestion(timeSuggestion) + .verifySystemClockWasNotSetAndResetCallTracking() + .assertLatestPhoneSuggestion(phoneId, null); + } + + @Test + public void testSuggestPhoneTime_systemClockThreshold() { + int systemClockUpdateThresholdMillis = 1000; + mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + .pokeThresholds(systemClockUpdateThresholdMillis) + .pokeAutoTimeDetectionEnabled(true); + + final int clockIncrement = 100; + int phoneId = ARBITRARY_PHONE_ID; + + // Send the first time signal. It should be used. + { + long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; + PhoneTimeSuggestion timeSuggestion1 = + mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); + TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime(); + + // Increment the the device clocks to simulate the passage of time. + mScript.simulateTimePassing(clockIncrement); + + long expectedSystemClockMillis1 = + TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis()); + + mScript.simulatePhoneTimeSuggestion(timeSuggestion1) + .verifySystemClockWasSetAndResetCallTracking( + expectedSystemClockMillis1, true /* expectNetworkBroadcast */) + .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); + } + + // Now send another time signal, but one that is too similar to the last one and should be + // stored, but not used to set the system clock. + { + int underThresholdMillis = systemClockUpdateThresholdMillis - 1; + PhoneTimeSuggestion timeSuggestion2 = mScript.generatePhoneTimeSuggestion( + phoneId, mScript.peekSystemClockMillis() + underThresholdMillis); + mScript.simulateTimePassing(clockIncrement) + .simulatePhoneTimeSuggestion(timeSuggestion2) + .verifySystemClockWasNotSetAndResetCallTracking() + .assertLatestPhoneSuggestion(phoneId, timeSuggestion2); + } + + // Now send another time signal, but one that is on the threshold and so should be used. + { + PhoneTimeSuggestion timeSuggestion3 = mScript.generatePhoneTimeSuggestion( + phoneId, + mScript.peekSystemClockMillis() + systemClockUpdateThresholdMillis); + mScript.simulateTimePassing(clockIncrement); + + long expectedSystemClockMillis3 = + TimeDetectorStrategy.getTimeAt(timeSuggestion3.getUtcTime(), + mScript.peekElapsedRealtimeMillis()); + + mScript.simulatePhoneTimeSuggestion(timeSuggestion3) + .verifySystemClockWasSetAndResetCallTracking( + expectedSystemClockMillis3, true /* expectNetworkBroadcast */) + .assertLatestPhoneSuggestion(phoneId, timeSuggestion3); + } + } + + @Test + public void testSuggestPhoneTime_multiplePhoneIdsAndBucketing() { + mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + .pokeAutoTimeDetectionEnabled(true); + + // There are 2 phones in this test. Phone 2 has a different idea of the current time. + // phone1Id < phone2Id (which is important because the strategy uses the lowest ID when + // multiple phone suggestions are available. + int phone1Id = ARBITRARY_PHONE_ID; + int phone2Id = ARBITRARY_PHONE_ID + 1; + long phone1TimeMillis = ARBITRARY_TEST_TIME_MILLIS; + long phone2TimeMillis = phone1TimeMillis + 60000; + + final int clockIncrement = 999; + + // Make a suggestion with phone2Id. + { + PhoneTimeSuggestion phone2TimeSuggestion = + mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis); + mScript.simulateTimePassing(clockIncrement); + + long expectedSystemClockMillis = phone2TimeMillis + clockIncrement; + + mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion) + .verifySystemClockWasSetAndResetCallTracking( + expectedSystemClockMillis, true /* expectNetworkBroadcast */) + .assertLatestPhoneSuggestion(phone1Id, null) + .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion); + } + + mScript.simulateTimePassing(clockIncrement); + + // Now make a different suggestion with phone1Id. + { + PhoneTimeSuggestion phone1TimeSuggestion = + mScript.generatePhoneTimeSuggestion(phone1Id, phone1TimeMillis); + mScript.simulateTimePassing(clockIncrement); + + long expectedSystemClockMillis = phone1TimeMillis + clockIncrement; + + mScript.simulatePhoneTimeSuggestion(phone1TimeSuggestion) + .verifySystemClockWasSetAndResetCallTracking( + expectedSystemClockMillis, true /* expectNetworkBroadcast */) + .assertLatestPhoneSuggestion(phone1Id, phone1TimeSuggestion); + + } + + mScript.simulateTimePassing(clockIncrement); + + // Make another suggestion with phone2Id. It should be stored but not used because the + // phone1Id suggestion will still "win". + { + PhoneTimeSuggestion phone2TimeSuggestion = + mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis); + mScript.simulateTimePassing(clockIncrement); + + mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion) + .verifySystemClockWasNotSetAndResetCallTracking() + .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion); + } + + // Let enough time pass that phone1Id's suggestion should now be too old. + mScript.simulateTimePassing(TimeDetectorStrategyImpl.PHONE_BUCKET_SIZE_MILLIS); + + // Make another suggestion with phone2Id. It should be used because the phoneId1 + // is in an older "bucket". + { + PhoneTimeSuggestion phone2TimeSuggestion = + mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis); + mScript.simulateTimePassing(clockIncrement); + + long expectedSystemClockMillis = phone2TimeMillis + clockIncrement; + + mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion) + .verifySystemClockWasSetAndResetCallTracking( + expectedSystemClockMillis, true /* expectNetworkBroadcast */) + .assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion); + } + } + + @Test + public void testSuggestPhoneTime_autoTimeDisabled() { + mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + .pokeAutoTimeDetectionEnabled(false); + + int phoneId = ARBITRARY_PHONE_ID; + PhoneTimeSuggestion timeSuggestion = + mScript.generatePhoneTimeSuggestion(phoneId, ARBITRARY_TEST_TIME_MILLIS); + mScript.simulateTimePassing(1000) + .simulatePhoneTimeSuggestion(timeSuggestion) + .verifySystemClockWasNotSetAndResetCallTracking() + .assertLatestPhoneSuggestion(phoneId, timeSuggestion); + } + + @Test + public void testSuggestPhoneTime_invalidNitzReferenceTimesIgnored() { + final int systemClockUpdateThreshold = 2000; + mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + .pokeThresholds(systemClockUpdateThreshold) + .pokeAutoTimeDetectionEnabled(true); + + long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; + int phoneId = ARBITRARY_PHONE_ID; + + PhoneTimeSuggestion timeSuggestion1 = + mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); + TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime(); + + // Initialize the strategy / device with a time set from a phone suggestion. + mScript.simulateTimePassing(100); + long expectedSystemClockMillis1 = + TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis()); + mScript.simulatePhoneTimeSuggestion(timeSuggestion1) + .verifySystemClockWasSetAndResetCallTracking( + expectedSystemClockMillis1, true /* expectNetworkBroadcast */) + .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); + + // The UTC time increment should be larger than the system clock update threshold so we + // know it shouldn't be ignored for other reasons. + long validUtcTimeMillis = utcTime1.getValue() + (2 * systemClockUpdateThreshold); + + // Now supply a new signal that has an obviously bogus reference time : older than the last + // one. + long referenceTimeBeforeLastSignalMillis = utcTime1.getReferenceTimeMillis() - 1; + TimestampedValue<Long> utcTime2 = new TimestampedValue<>( + referenceTimeBeforeLastSignalMillis, validUtcTimeMillis); + PhoneTimeSuggestion timeSuggestion2 = + createPhoneTimeSuggestion(phoneId, utcTime2); + mScript.simulatePhoneTimeSuggestion(timeSuggestion2) + .verifySystemClockWasNotSetAndResetCallTracking() + .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); + + // Now supply a new signal that has an obviously bogus reference time : substantially in the + // future. + long referenceTimeInFutureMillis = + utcTime1.getReferenceTimeMillis() + Integer.MAX_VALUE + 1; + TimestampedValue<Long> utcTime3 = new TimestampedValue<>( + referenceTimeInFutureMillis, validUtcTimeMillis); + PhoneTimeSuggestion timeSuggestion3 = + createPhoneTimeSuggestion(phoneId, utcTime3); + mScript.simulatePhoneTimeSuggestion(timeSuggestion3) + .verifySystemClockWasNotSetAndResetCallTracking() + .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); + + // Just to prove validUtcTimeMillis is valid. + long validReferenceTimeMillis = utcTime1.getReferenceTimeMillis() + 100; + TimestampedValue<Long> utcTime4 = new TimestampedValue<>( + validReferenceTimeMillis, validUtcTimeMillis); + long expectedSystemClockMillis4 = + TimeDetectorStrategy.getTimeAt(utcTime4, mScript.peekElapsedRealtimeMillis()); + PhoneTimeSuggestion timeSuggestion4 = + createPhoneTimeSuggestion(phoneId, utcTime4); + mScript.simulatePhoneTimeSuggestion(timeSuggestion4) + .verifySystemClockWasSetAndResetCallTracking( + expectedSystemClockMillis4, true /* expectNetworkBroadcast */) + .assertLatestPhoneSuggestion(phoneId, timeSuggestion4); + } + + @Test + public void testSuggestPhoneTime_timeDetectionToggled() { + final int clockIncrementMillis = 100; + final int systemClockUpdateThreshold = 2000; + mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + .pokeThresholds(systemClockUpdateThreshold) + .pokeAutoTimeDetectionEnabled(false); + + int phoneId = ARBITRARY_PHONE_ID; + long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; + PhoneTimeSuggestion timeSuggestion1 = + mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); + TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime(); + + // Simulate time passing. + mScript.simulateTimePassing(clockIncrementMillis); + + // Simulate the time signal being received. It should not be used because auto time + // detection is off but it should be recorded. + mScript.simulatePhoneTimeSuggestion(timeSuggestion1) + .verifySystemClockWasNotSetAndResetCallTracking() + .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); + + // Simulate more time passing. + mScript.simulateTimePassing(clockIncrementMillis); + + long expectedSystemClockMillis1 = + TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis()); + + // Turn on auto time detection. + mScript.simulateAutoTimeDetectionToggle() + .verifySystemClockWasSetAndResetCallTracking( + expectedSystemClockMillis1, true /* expectNetworkBroadcast */) + .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); + + // Turn off auto time detection. + mScript.simulateAutoTimeDetectionToggle() + .verifySystemClockWasNotSetAndResetCallTracking() + .assertLatestPhoneSuggestion(phoneId, timeSuggestion1); + + // Receive another valid time signal. + // It should be on the threshold and accounting for the clock increments. + PhoneTimeSuggestion timeSuggestion2 = mScript.generatePhoneTimeSuggestion( + phoneId, mScript.peekSystemClockMillis() + systemClockUpdateThreshold); + + // Simulate more time passing. + mScript.simulateTimePassing(clockIncrementMillis); + + long expectedSystemClockMillis2 = TimeDetectorStrategy.getTimeAt( + timeSuggestion2.getUtcTime(), mScript.peekElapsedRealtimeMillis()); + + // The new time, though valid, should not be set in the system clock because auto time is + // disabled. + mScript.simulatePhoneTimeSuggestion(timeSuggestion2) + .verifySystemClockWasNotSetAndResetCallTracking() + .assertLatestPhoneSuggestion(phoneId, timeSuggestion2); + + // Turn on auto time detection. + mScript.simulateAutoTimeDetectionToggle() + .verifySystemClockWasSetAndResetCallTracking( + expectedSystemClockMillis2, true /* expectNetworkBroadcast */) + .assertLatestPhoneSuggestion(phoneId, timeSuggestion2); + } + + @Test + public void testSuggestPhoneTime_maxSuggestionAge() { + mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + .pokeAutoTimeDetectionEnabled(true); + + int phoneId = ARBITRARY_PHONE_ID; + long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; + PhoneTimeSuggestion phoneSuggestion = + mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); + int clockIncrementMillis = 1000; + + mScript.simulateTimePassing(clockIncrementMillis) + .simulatePhoneTimeSuggestion(phoneSuggestion) + .verifySystemClockWasSetAndResetCallTracking( + testTimeMillis + clockIncrementMillis, true /* expectedNetworkBroadcast */) + .assertLatestPhoneSuggestion(phoneId, phoneSuggestion); + + // Look inside and check what the strategy considers the current best phone suggestion. + assertEquals(phoneSuggestion, mScript.peekBestPhoneSuggestion()); + + // Simulate time passing, long enough that phoneSuggestion is now too old. + mScript.simulateTimePassing(TimeDetectorStrategyImpl.PHONE_MAX_AGE_MILLIS); + + // Look inside and check what the strategy considers the current best phone suggestion. It + // should still be the, it's just no longer used. + assertNull(mScript.peekBestPhoneSuggestion()); + mScript.assertLatestPhoneSuggestion(phoneId, phoneSuggestion); + } + + @Test + public void testSuggestManualTime_autoTimeDisabled() { + mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + .pokeAutoTimeDetectionEnabled(false); + + long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; + ManualTimeSuggestion timeSuggestion = mScript.generateManualTimeSuggestion(testTimeMillis); + final int clockIncrement = 1000; + long expectedSystemClockMillis = testTimeMillis + clockIncrement; + + mScript.simulateTimePassing(clockIncrement) + .simulateManualTimeSuggestion(timeSuggestion) + .verifySystemClockWasSetAndResetCallTracking( + expectedSystemClockMillis, false /* expectNetworkBroadcast */); + } + + @Test + public void testSuggestManualTime_retainsAutoSignal() { + // Configure the start state. + mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + .pokeAutoTimeDetectionEnabled(true); + + int phoneId = ARBITRARY_PHONE_ID; + + // Simulate a phone suggestion. + long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS; + PhoneTimeSuggestion phoneTimeSuggestion = + mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis); + long expectedAutoClockMillis = phoneTimeSuggestion.getUtcTime().getValue(); + final int clockIncrement = 1000; + + // Simulate the passage of time. + mScript.simulateTimePassing(clockIncrement); + expectedAutoClockMillis += clockIncrement; + + mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion) + .verifySystemClockWasSetAndResetCallTracking( + expectedAutoClockMillis, true /* expectNetworkBroadcast */) + .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); + + // Simulate the passage of time. + mScript.simulateTimePassing(clockIncrement); + expectedAutoClockMillis += clockIncrement; + + // Switch to manual. + mScript.simulateAutoTimeDetectionToggle() + .verifySystemClockWasNotSetAndResetCallTracking() + .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); + + // Simulate the passage of time. + mScript.simulateTimePassing(clockIncrement); + expectedAutoClockMillis += clockIncrement; + + // Simulate a manual suggestion 1 day different from the auto suggestion. + long manualTimeMillis = testTimeMillis + ONE_DAY_MILLIS; + long expectedManualClockMillis = manualTimeMillis; + ManualTimeSuggestion manualTimeSuggestion = + mScript.generateManualTimeSuggestion(manualTimeMillis); + mScript.simulateManualTimeSuggestion(manualTimeSuggestion) + .verifySystemClockWasSetAndResetCallTracking( + expectedManualClockMillis, false /* expectNetworkBroadcast */) + .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); + + // Simulate the passage of time. + mScript.simulateTimePassing(clockIncrement); + expectedAutoClockMillis += clockIncrement; + + // Switch back to auto. + mScript.simulateAutoTimeDetectionToggle(); + + mScript.verifySystemClockWasSetAndResetCallTracking( + expectedAutoClockMillis, true /* expectNetworkBroadcast */) + .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); + + // Switch back to manual - nothing should happen to the clock. + mScript.simulateAutoTimeDetectionToggle() + .verifySystemClockWasNotSetAndResetCallTracking() + .assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion); + } + + /** + * Manual suggestions should be ignored if auto time is enabled. + */ + @Test + public void testSuggestManualTime_autoTimeEnabled() { + mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO) + .pokeAutoTimeDetectionEnabled(true); + + ManualTimeSuggestion timeSuggestion = + mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS); + final int clockIncrement = 1000; + + mScript.simulateTimePassing(clockIncrement) + .simulateManualTimeSuggestion(timeSuggestion) + .verifySystemClockWasNotSetAndResetCallTracking(); + } + + /** + * A fake implementation of TimeDetectorStrategy.Callback. Besides tracking changes and behaving + * like the real thing should, it also asserts preconditions. + */ + private static class FakeCallback implements TimeDetectorStrategy.Callback { + private boolean mAutoTimeDetectionEnabled; + private boolean mWakeLockAcquired; + private long mElapsedRealtimeMillis; + private long mSystemClockMillis; + private int mSystemClockUpdateThresholdMillis = 2000; + + // Tracking operations. + private boolean mSystemClockWasSet; + private Intent mBroadcastSent; + + @Override + public int systemClockUpdateThresholdMillis() { + return mSystemClockUpdateThresholdMillis; + } + + @Override + public boolean isAutoTimeDetectionEnabled() { + return mAutoTimeDetectionEnabled; + } + + @Override + public void acquireWakeLock() { + if (mWakeLockAcquired) { + fail("Wake lock already acquired"); + } + mWakeLockAcquired = true; + } + + @Override + public long elapsedRealtimeMillis() { + return mElapsedRealtimeMillis; + } + + @Override + public long systemClockMillis() { + assertWakeLockAcquired(); + return mSystemClockMillis; + } + + @Override + public void setSystemClock(long newTimeMillis) { + assertWakeLockAcquired(); + mSystemClockWasSet = true; + mSystemClockMillis = newTimeMillis; + } + + @Override + public void releaseWakeLock() { + assertWakeLockAcquired(); + mWakeLockAcquired = false; + } + + @Override + public void sendStickyBroadcast(Intent intent) { + assertNotNull(intent); + mBroadcastSent = intent; + } + + // Methods below are for managing the fake's behavior. + + void pokeSystemClockUpdateThreshold(int thresholdMillis) { + mSystemClockUpdateThresholdMillis = thresholdMillis; + } + + void pokeElapsedRealtimeMillis(long elapsedRealtimeMillis) { + mElapsedRealtimeMillis = elapsedRealtimeMillis; + } + + void pokeSystemClockMillis(long systemClockMillis) { + mSystemClockMillis = systemClockMillis; + } + + void pokeAutoTimeDetectionEnabled(boolean enabled) { + mAutoTimeDetectionEnabled = enabled; + } + + long peekElapsedRealtimeMillis() { + return mElapsedRealtimeMillis; + } + + long peekSystemClockMillis() { + return mSystemClockMillis; + } + + void simulateTimePassing(long incrementMillis) { + mElapsedRealtimeMillis += incrementMillis; + mSystemClockMillis += incrementMillis; + } + + void simulateAutoTimeZoneDetectionToggle() { + mAutoTimeDetectionEnabled = !mAutoTimeDetectionEnabled; + } + + void verifySystemClockNotSet() { + assertFalse(mSystemClockWasSet); + } + + void verifySystemClockWasSet(long expectedSystemClockMillis) { + assertTrue(mSystemClockWasSet); + assertEquals(expectedSystemClockMillis, mSystemClockMillis); + } + + void verifyIntentWasBroadcast() { + assertTrue(mBroadcastSent != null); + } + + void verifyIntentWasNotBroadcast() { + assertNull(mBroadcastSent); + } + + void resetCallTracking() { + mSystemClockWasSet = false; + mBroadcastSent = null; + } + + private void assertWakeLockAcquired() { + assertTrue("The operation must be performed only after acquiring the wakelock", + mWakeLockAcquired); + } + } + + /** + * A fluent helper class for tests. + */ + private class Script { + + private final FakeCallback mFakeCallback; + private final TimeDetectorStrategyImpl mTimeDetectorStrategy; + + Script() { + mFakeCallback = new FakeCallback(); + mTimeDetectorStrategy = new TimeDetectorStrategyImpl(); + mTimeDetectorStrategy.initialize(mFakeCallback); + + } + + Script pokeAutoTimeDetectionEnabled(boolean enabled) { + mFakeCallback.pokeAutoTimeDetectionEnabled(enabled); + return this; + } + + Script pokeFakeClocks(TimestampedValue<Long> timeInfo) { + mFakeCallback.pokeElapsedRealtimeMillis(timeInfo.getReferenceTimeMillis()); + mFakeCallback.pokeSystemClockMillis(timeInfo.getValue()); + return this; + } + + Script pokeThresholds(int systemClockUpdateThreshold) { + mFakeCallback.pokeSystemClockUpdateThreshold(systemClockUpdateThreshold); + return this; + } + + long peekElapsedRealtimeMillis() { + return mFakeCallback.peekElapsedRealtimeMillis(); + } + + long peekSystemClockMillis() { + return mFakeCallback.peekSystemClockMillis(); + } + + Script simulatePhoneTimeSuggestion(PhoneTimeSuggestion timeSuggestion) { + mTimeDetectorStrategy.suggestPhoneTime(timeSuggestion); + return this; + } + + Script simulateManualTimeSuggestion(ManualTimeSuggestion timeSuggestion) { + mTimeDetectorStrategy.suggestManualTime(timeSuggestion); + return this; + } + + Script simulateAutoTimeDetectionToggle() { + mFakeCallback.simulateAutoTimeZoneDetectionToggle(); + mTimeDetectorStrategy.handleAutoTimeDetectionChanged(); + return this; + } + + Script simulateTimePassing(long clockIncrementMillis) { + mFakeCallback.simulateTimePassing(clockIncrementMillis); + return this; + } + + Script verifySystemClockWasNotSetAndResetCallTracking() { + mFakeCallback.verifySystemClockNotSet(); + mFakeCallback.verifyIntentWasNotBroadcast(); + mFakeCallback.resetCallTracking(); + return this; + } + + Script verifySystemClockWasSetAndResetCallTracking( + long expectedSystemClockMillis, boolean expectNetworkBroadcast) { + mFakeCallback.verifySystemClockWasSet(expectedSystemClockMillis); + if (expectNetworkBroadcast) { + mFakeCallback.verifyIntentWasBroadcast(); + } + mFakeCallback.resetCallTracking(); + return this; + } + + /** + * White box test info: Asserts the latest suggestion for the phone ID is as expected. + */ + Script assertLatestPhoneSuggestion(int phoneId, PhoneTimeSuggestion expected) { + assertEquals(expected, mTimeDetectorStrategy.getLatestPhoneSuggestion(phoneId)); + return this; + } + + /** + * White box test info: Returns the phone suggestion that would be used, if any, given the + * current elapsed real time clock. + */ + PhoneTimeSuggestion peekBestPhoneSuggestion() { + return mTimeDetectorStrategy.findBestPhoneSuggestionForTests(); + } + + /** + * Generates a ManualTimeSuggestion using the current elapsed realtime clock for the + * reference time. + */ + ManualTimeSuggestion generateManualTimeSuggestion(long timeMillis) { + TimestampedValue<Long> utcTime = + new TimestampedValue<>(mFakeCallback.peekElapsedRealtimeMillis(), timeMillis); + return new ManualTimeSuggestion(utcTime); + } + + /** + * Generates a PhoneTimeSuggestion using the current elapsed realtime clock for the + * reference time. + */ + PhoneTimeSuggestion generatePhoneTimeSuggestion(int phoneId, Long timeMillis) { + TimestampedValue<Long> time = null; + if (timeMillis != null) { + time = new TimestampedValue<>(peekElapsedRealtimeMillis(), timeMillis); + } + return createPhoneTimeSuggestion(phoneId, time); + } + } + + private static PhoneTimeSuggestion createPhoneTimeSuggestion(int phoneId, + TimestampedValue<Long> utcTime) { + return new PhoneTimeSuggestion.Builder(phoneId) + .setUtcTime(utcTime) + .build(); + } + + private static long createUtcTime(int year, int monthInYear, int day, int hourOfDay, int minute, + int second) { + Calendar cal = new GregorianCalendar(TimeZone.getTimeZone("Etc/UTC")); + cal.clear(); + cal.set(year, monthInYear - 1, day, hourOfDay, minute, second); + return cal.getTimeInMillis(); + } +} diff --git a/telephony/java/android/telephony/Annotation.java b/telephony/java/android/telephony/Annotation.java index 73faf9f5f9f4..3940a3b0a1cf 100644 --- a/telephony/java/android/telephony/Annotation.java +++ b/telephony/java/android/telephony/Annotation.java @@ -116,7 +116,8 @@ public class Annotation { ApnSetting.TYPE_CBS, ApnSetting.TYPE_IA, ApnSetting.TYPE_EMERGENCY, - ApnSetting.TYPE_MCX + ApnSetting.TYPE_MCX, + ApnSetting.TYPE_XCAP, }) @Retention(RetentionPolicy.SOURCE) public @interface ApnType { diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 4071e957837c..261e8d008187 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2989,7 +2989,6 @@ public class CarrierConfigManager { /** * Location information during (and after) an emergency call is only provided over control * plane signaling from the network. - * @hide */ public static final int SUPL_EMERGENCY_MODE_TYPE_CP_ONLY = 0; @@ -2997,7 +2996,6 @@ public class CarrierConfigManager { * Location information during (and after) an emergency call is provided over the data * plane and serviced by the framework GNSS service, but if it fails, the carrier also * supports control plane backup signaling. - * @hide */ public static final int SUPL_EMERGENCY_MODE_TYPE_CP_FALLBACK = 1; @@ -3005,7 +3003,6 @@ public class CarrierConfigManager { * Location information during (and after) an emergency call is provided over the data plane * and serviced by the framework GNSS service only. There is no backup signalling over the * control plane if it fails. - * @hide */ public static final int SUPL_EMERGENCY_MODE_TYPE_DP_ONLY = 2; @@ -3113,11 +3110,21 @@ public class CarrierConfigManager { * {@link #SUPL_EMERGENCY_MODE_TYPE_CP_ONLY}. * <p> * The default value for this configuration is {@link #SUPL_EMERGENCY_MODE_TYPE_CP_ONLY}. - * @hide */ public static final String KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT = KEY_PREFIX + "es_supl_control_plane_support_int"; + /** + * A list of roaming PLMNs where SUPL ES mode does not support a control-plane mechanism to + * get a user's location in the event that data plane SUPL fails or is otherwise + * unavailable. + * <p> + * A string array of PLMNs that do not support a control-plane mechanism for getting a + * user's location for SUPL ES. + */ + public static final String KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY = + KEY_PREFIX + "es_supl_data_plane_only_roaming_plmn_string_array"; + private static PersistableBundle getDefaults() { PersistableBundle defaults = new PersistableBundle(); defaults.putBoolean(KEY_PERSIST_LPP_MODE_BOOL, true); @@ -3134,6 +3141,7 @@ public class CarrierConfigManager { defaults.putString(KEY_NFW_PROXY_APPS_STRING, ""); defaults.putInt(KEY_ES_SUPL_CONTROL_PLANE_SUPPORT_INT, SUPL_EMERGENCY_MODE_TYPE_CP_ONLY); + defaults.putStringArray(KEY_ES_SUPL_DATA_PLANE_ONLY_ROAMING_PLMN_STRING_ARRAY, null); return defaults; } } diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java index bbf746fcf3c4..fc717e7465b1 100644 --- a/telephony/java/android/telephony/NetworkRegistrationInfo.java +++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java @@ -24,8 +24,8 @@ import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; import android.telephony.AccessNetworkConstants.TransportType; - import android.telephony.Annotation.NetworkType; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -90,7 +90,7 @@ public final class NetworkRegistrationInfo implements Parcelable { * Dual Connectivity(EN-DC). * @hide */ - public static final int NR_STATE_NONE = -1; + public static final int NR_STATE_NONE = 0; /** * The device is camped on an LTE cell that supports E-UTRA-NR Dual Connectivity(EN-DC) but diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 1e601cf3de5f..9ace86cea0c1 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -102,7 +102,7 @@ public class ServiceState implements Parcelable { * Indicates frequency range is unknown. * @hide */ - public static final int FREQUENCY_RANGE_UNKNOWN = -1; + public static final int FREQUENCY_RANGE_UNKNOWN = 0; /** * Indicates the frequency range is below 1GHz. diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index f1abe04cdafa..4fb26fdf1d2c 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -703,31 +703,6 @@ public class TelephonyManager { public static final String EXTRA_INCOMING_NUMBER = "incoming_number"; /** - * Broadcast intent action indicating that a precise call state - * (cellular) on the device has changed. - * - * <p> - * The {@link #EXTRA_RINGING_CALL_STATE} extra indicates the ringing call state. - * The {@link #EXTRA_FOREGROUND_CALL_STATE} extra indicates the foreground call state. - * The {@link #EXTRA_BACKGROUND_CALL_STATE} extra indicates the background call state. - * - * <p class="note"> - * Requires the READ_PRECISE_PHONE_STATE permission. - * - * @see #EXTRA_RINGING_CALL_STATE - * @see #EXTRA_FOREGROUND_CALL_STATE - * @see #EXTRA_BACKGROUND_CALL_STATE - * - * <p class="note"> - * Requires the READ_PRECISE_PHONE_STATE permission. - * @deprecated use {@link PhoneStateListener#LISTEN_PRECISE_CALL_STATE} instead - * @hide - */ - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - public static final String ACTION_PRECISE_CALL_STATE_CHANGED = - "android.intent.action.PRECISE_CALL_STATE"; - - /** * Broadcast intent action indicating that call disconnect cause has changed. * * <p> @@ -749,78 +724,6 @@ public class TelephonyManager { /** * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and * {@link PhoneStateListener#onPreciseCallStateChanged(PreciseCallState)} for an integer - * containing the state of the current ringing call. - * - * @see PreciseCallState#PRECISE_CALL_STATE_NOT_VALID - * @see PreciseCallState#PRECISE_CALL_STATE_IDLE - * @see PreciseCallState#PRECISE_CALL_STATE_ACTIVE - * @see PreciseCallState#PRECISE_CALL_STATE_HOLDING - * @see PreciseCallState#PRECISE_CALL_STATE_DIALING - * @see PreciseCallState#PRECISE_CALL_STATE_ALERTING - * @see PreciseCallState#PRECISE_CALL_STATE_INCOMING - * @see PreciseCallState#PRECISE_CALL_STATE_WAITING - * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTED - * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTING - * - * <p class="note"> - * Retrieve with - * {@link android.content.Intent#getIntExtra(String name, int defaultValue)}. - * - * @hide - */ - public static final String EXTRA_RINGING_CALL_STATE = "ringing_state"; - - /** - * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and - * {@link PhoneStateListener#onPreciseCallStateChanged(PreciseCallState)} for an integer - * containing the state of the current foreground call. - * - * @see PreciseCallState#PRECISE_CALL_STATE_NOT_VALID - * @see PreciseCallState#PRECISE_CALL_STATE_IDLE - * @see PreciseCallState#PRECISE_CALL_STATE_ACTIVE - * @see PreciseCallState#PRECISE_CALL_STATE_HOLDING - * @see PreciseCallState#PRECISE_CALL_STATE_DIALING - * @see PreciseCallState#PRECISE_CALL_STATE_ALERTING - * @see PreciseCallState#PRECISE_CALL_STATE_INCOMING - * @see PreciseCallState#PRECISE_CALL_STATE_WAITING - * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTED - * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTING - * - * <p class="note"> - * Retrieve with - * {@link android.content.Intent#getIntExtra(String name, int defaultValue)}. - * - * @hide - */ - public static final String EXTRA_FOREGROUND_CALL_STATE = "foreground_state"; - - /** - * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and - * {@link PhoneStateListener#onPreciseCallStateChanged(PreciseCallState)} for an integer - * containing the state of the current background call. - * - * @see PreciseCallState#PRECISE_CALL_STATE_NOT_VALID - * @see PreciseCallState#PRECISE_CALL_STATE_IDLE - * @see PreciseCallState#PRECISE_CALL_STATE_ACTIVE - * @see PreciseCallState#PRECISE_CALL_STATE_HOLDING - * @see PreciseCallState#PRECISE_CALL_STATE_DIALING - * @see PreciseCallState#PRECISE_CALL_STATE_ALERTING - * @see PreciseCallState#PRECISE_CALL_STATE_INCOMING - * @see PreciseCallState#PRECISE_CALL_STATE_WAITING - * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTED - * @see PreciseCallState#PRECISE_CALL_STATE_DISCONNECTING - * - * <p class="note"> - * Retrieve with - * {@link android.content.Intent#getIntExtra(String name, int defaultValue)}. - * - * @hide - */ - public static final String EXTRA_BACKGROUND_CALL_STATE = "background_state"; - - /** - * The lookup key used with the {@link #ACTION_PRECISE_CALL_STATE_CHANGED} broadcast and - * {@link PhoneStateListener#onPreciseCallStateChanged(PreciseCallState)} for an integer * containing the disconnect cause. * * @see DisconnectCause @@ -849,88 +752,6 @@ public class TelephonyManager { public static final String EXTRA_PRECISE_DISCONNECT_CAUSE = "precise_disconnect_cause"; /** - * Broadcast intent action indicating a data connection has changed, - * providing precise information about the connection. - * - * <p> - * The {@link #EXTRA_DATA_STATE} extra indicates the connection state. - * The {@link #EXTRA_DATA_NETWORK_TYPE} extra indicates the connection network type. - * The {@link #EXTRA_DATA_APN_TYPE} extra indicates the APN type. - * The {@link #EXTRA_DATA_APN} extra indicates the APN. - * The {@link #EXTRA_DATA_IFACE_PROPERTIES} extra indicates the connection interface. - * The {@link #EXTRA_DATA_FAILURE_CAUSE} extra indicates the connection fail cause. - * - * <p class="note"> - * Requires the READ_PRECISE_PHONE_STATE permission. - * - * @see #EXTRA_DATA_STATE - * @see #EXTRA_DATA_NETWORK_TYPE - * @see #EXTRA_DATA_APN_TYPE - * @see #EXTRA_DATA_APN - * @see #EXTRA_DATA_IFACE - * @see #EXTRA_DATA_FAILURE_CAUSE - * @hide - * - * @deprecated If the app is running in the background, it won't be able to receive this - * broadcast. Apps should use ConnectivityManager {@link #registerNetworkCallback( - * android.net.NetworkRequest, ConnectivityManager.NetworkCallback)} to listen for network - * changes. - */ - @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) - @Deprecated - @UnsupportedAppUsage - public static final String ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED = - "android.intent.action.PRECISE_DATA_CONNECTION_STATE_CHANGED"; - - /** - * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast - * for an integer containing the state of the current data connection. - * - * @see TelephonyManager#DATA_UNKNOWN - * @see TelephonyManager#DATA_DISCONNECTED - * @see TelephonyManager#DATA_CONNECTING - * @see TelephonyManager#DATA_CONNECTED - * @see TelephonyManager#DATA_SUSPENDED - * - * <p class="note"> - * Retrieve with - * {@link android.content.Intent#getIntExtra(String name, int defaultValue)}. - * - * @hide - */ - public static final String EXTRA_DATA_STATE = PhoneConstants.STATE_KEY; - - /** - * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast - * for an integer containing the network type. - * - * @see TelephonyManager#NETWORK_TYPE_UNKNOWN - * @see TelephonyManager#NETWORK_TYPE_GPRS - * @see TelephonyManager#NETWORK_TYPE_EDGE - * @see TelephonyManager#NETWORK_TYPE_UMTS - * @see TelephonyManager#NETWORK_TYPE_CDMA - * @see TelephonyManager#NETWORK_TYPE_EVDO_0 - * @see TelephonyManager#NETWORK_TYPE_EVDO_A - * @see TelephonyManager#NETWORK_TYPE_1xRTT - * @see TelephonyManager#NETWORK_TYPE_HSDPA - * @see TelephonyManager#NETWORK_TYPE_HSUPA - * @see TelephonyManager#NETWORK_TYPE_HSPA - * @see TelephonyManager#NETWORK_TYPE_IDEN - * @see TelephonyManager#NETWORK_TYPE_EVDO_B - * @see TelephonyManager#NETWORK_TYPE_LTE - * @see TelephonyManager#NETWORK_TYPE_EHRPD - * @see TelephonyManager#NETWORK_TYPE_HSPAP - * @see TelephonyManager#NETWORK_TYPE_NR - * - * <p class="note"> - * Retrieve with - * {@link android.content.Intent#getIntExtra(String name, int defaultValue)}. - * - * @hide - */ - public static final String EXTRA_DATA_NETWORK_TYPE = PhoneConstants.DATA_NETWORK_TYPE_KEY; - - /** * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast * for an String containing the data APN type. * @@ -967,18 +788,6 @@ public class TelephonyManager { public static final String EXTRA_DATA_LINK_PROPERTIES_KEY = PhoneConstants.DATA_LINK_PROPERTIES_KEY; /** - * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast - * for the data connection fail cause. - * - * <p class="note"> - * Retrieve with - * {@link android.content.Intent#getStringExtra(String name)}. - * - * @hide - */ - public static final String EXTRA_DATA_FAILURE_CAUSE = PhoneConstants.DATA_FAILURE_CAUSE_KEY; - - /** * Broadcast intent action for letting the default dialer to know to show voicemail * notification. * diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java index 60774e7f1e7f..034fc220cbbe 100644 --- a/telephony/java/android/telephony/data/ApnSetting.java +++ b/telephony/java/android/telephony/data/ApnSetting.java @@ -20,7 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ContentValues; import android.database.Cursor; -import android.hardware.radio.V1_4.ApnTypes; +import android.hardware.radio.V1_5.ApnTypes; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; @@ -110,6 +110,8 @@ public class ApnSetting implements Parcelable { public static final int TYPE_EMERGENCY = ApnTypes.EMERGENCY; /** APN type for MCX (Mission Critical Service) where X can be PTT/Video/Data */ public static final int TYPE_MCX = ApnTypes.MCX; + /** APN type for XCAP. */ + public static final int TYPE_XCAP = ApnTypes.XCAP; // Possible values for authentication types. /** No authentication type. */ @@ -198,6 +200,7 @@ public class ApnSetting implements Parcelable { APN_TYPE_STRING_MAP.put("ia", TYPE_IA); APN_TYPE_STRING_MAP.put("emergency", TYPE_EMERGENCY); APN_TYPE_STRING_MAP.put("mcx", TYPE_MCX); + APN_TYPE_STRING_MAP.put("xcap", TYPE_XCAP); APN_TYPE_INT_MAP = new ArrayMap<Integer, String>(); APN_TYPE_INT_MAP.put(TYPE_DEFAULT, "default"); APN_TYPE_INT_MAP.put(TYPE_MMS, "mms"); @@ -210,6 +213,7 @@ public class ApnSetting implements Parcelable { APN_TYPE_INT_MAP.put(TYPE_IA, "ia"); APN_TYPE_INT_MAP.put(TYPE_EMERGENCY, "emergency"); APN_TYPE_INT_MAP.put(TYPE_MCX, "mcx"); + APN_TYPE_INT_MAP.put(TYPE_XCAP, "xcap"); PROTOCOL_STRING_MAP = new ArrayMap<String, Integer>(); PROTOCOL_STRING_MAP.put("IP", PROTOCOL_IP); @@ -1944,8 +1948,9 @@ public class ApnSetting implements Parcelable { * {@link ApnSetting} built from this builder otherwise. */ public ApnSetting build() { - if ((mApnTypeBitmask & (TYPE_DEFAULT | TYPE_MMS | TYPE_SUPL | TYPE_DUN | TYPE_HIPRI | - TYPE_FOTA | TYPE_IMS | TYPE_CBS | TYPE_IA | TYPE_EMERGENCY | TYPE_MCX)) == 0 + if ((mApnTypeBitmask & (TYPE_DEFAULT | TYPE_MMS | TYPE_SUPL | TYPE_DUN | TYPE_HIPRI + | TYPE_FOTA | TYPE_IMS | TYPE_CBS | TYPE_IA | TYPE_EMERGENCY | TYPE_MCX + | TYPE_XCAP)) == 0 || TextUtils.isEmpty(mApnName) || TextUtils.isEmpty(mEntryName)) { return null; } diff --git a/telephony/java/android/telephony/ims/ImsReasonInfo.java b/telephony/java/android/telephony/ims/ImsReasonInfo.java index 76b214b71075..f4b2cef73a44 100644 --- a/telephony/java/android/telephony/ims/ImsReasonInfo.java +++ b/telephony/java/android/telephony/ims/ImsReasonInfo.java @@ -889,6 +889,12 @@ public final class ImsReasonInfo implements Parcelable { */ public static final int CODE_WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION = 1623; + /** + * The dialed RTT call should be retried without RTT + * @hide + */ + public static final int CODE_RETRY_ON_IMS_WITHOUT_RTT = 3001; + /* * OEM specific error codes. To be used by OEMs when they don't want to reveal error code which * would be replaced by ERROR_UNSPECIFIED. @@ -1069,6 +1075,7 @@ public final class ImsReasonInfo implements Parcelable { CODE_REJECT_VT_AVPF_NOT_ALLOWED, CODE_REJECT_ONGOING_ENCRYPTED_CALL, CODE_REJECT_ONGOING_CS_CALL, + CODE_RETRY_ON_IMS_WITHOUT_RTT, CODE_OEM_CAUSE_1, CODE_OEM_CAUSE_2, CODE_OEM_CAUSE_3, diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java index 05e2ed9acb33..6e635143a8e5 100644 --- a/telephony/java/com/android/internal/telephony/PhoneConstants.java +++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java @@ -161,6 +161,8 @@ public class PhoneConstants { public static final String APN_TYPE_EMERGENCY = "emergency"; /** APN type for Mission Critical Services */ public static final String APN_TYPE_MCX = "mcx"; + /** APN type for XCAP */ + public static final String APN_TYPE_XCAP = "xcap"; /** Array of all APN types */ public static final String[] APN_TYPES = {APN_TYPE_DEFAULT, APN_TYPE_MMS, @@ -172,7 +174,8 @@ public class PhoneConstants { APN_TYPE_CBS, APN_TYPE_IA, APN_TYPE_EMERGENCY, - APN_TYPE_MCX + APN_TYPE_MCX, + APN_TYPE_XCAP, }; public static final int RIL_CARD_MAX_APPS = 8; diff --git a/test-mock/Android.bp b/test-mock/Android.bp index aa4174ad40f4..616b6b005f89 100644 --- a/test-mock/Android.bp +++ b/test-mock/Android.bp @@ -30,6 +30,7 @@ java_sdk_library { libs: [ "framework-all", "app-compat-annotations", + "unsupportedappusage", ], api_packages: [ |