diff options
68 files changed, 2644 insertions, 867 deletions
diff --git a/StubLibraries.bp b/StubLibraries.bp index bb13eaacfa90..26478d3bad26 100644 --- a/StubLibraries.bp +++ b/StubLibraries.bp @@ -150,6 +150,9 @@ priv_apps = " " + module_libs = " " + " --show-annotation android.annotation.SystemApi\\(" + "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES" + + "\\)" + + " --show-for-stub-purposes-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" + "\\) " droidstubs { @@ -222,16 +225,10 @@ droidstubs { } ///////////////////////////////////////////////////////////////////// -// Following droidstubs modules are for extra APIs for modules. -// The framework currently have two more API surfaces for modules: -// @SystemApi(client=MODULE_APPS) and @SystemApi(client=MODULE_LIBRARIES) +// Following droidstubs modules are for extra APIs for modules, +// namely @SystemApi(client=MODULE_LIBRARIES) APIs. ///////////////////////////////////////////////////////////////////// -// TODO(b/146727827) remove the *-api module when we can teach metalava -// about the relationship among the API surfaces. Currently, these modules are only to generate -// the API signature files and ensure that the APIs evolve in a backwards compatible manner. -// They however are NOT used for building the API stub. - droidstubs { name: "module-lib-api", defaults: ["metalava-full-api-stubs-default"], @@ -268,7 +265,7 @@ droidstubs { name: "module-lib-api-stubs-docs-non-updatable", defaults: ["metalava-non-updatable-api-stubs-default"], arg_files: ["core/res/AndroidManifest.xml"], - args: metalava_framework_docs_args + module_libs, + args: metalava_framework_docs_args + priv_apps + module_libs, check_api: { current: { api_file: "non-updatable-api/module-lib-current.txt", @@ -277,17 +274,6 @@ droidstubs { }, } -// The following droidstub module generates source files for the API stub library for -// modules. Note that it not only includes its own APIs but also other APIs that have -// narrower scope (all @SystemApis, not just the ones with 'client=MODULE_LIBRARIES'). - -droidstubs { - name: "module-lib-api-stubs-docs", - defaults: ["metalava-non-updatable-api-stubs-default"], - arg_files: ["core/res/AndroidManifest.xml"], - args: metalava_framework_docs_args + priv_apps + module_libs, -} - ///////////////////////////////////////////////////////////////////// // android_*_stubs_current modules are the stubs libraries compiled // from *-api-stubs-docs @@ -333,7 +319,7 @@ java_library_static { java_library_static { name: "android_module_lib_stubs_current", - srcs: [ ":module-lib-api-stubs-docs" ], + srcs: [ ":module-lib-api-stubs-docs-non-updatable" ], defaults: ["android_defaults_stubs_current"], libs: ["sdk_system_29_android"], } diff --git a/apex/Android.bp b/apex/Android.bp index 380b4c6cc0be..f34ecbd79108 100644 --- a/apex/Android.bp +++ b/apex/Android.bp @@ -53,6 +53,9 @@ priv_apps = " " + module_libs = " " + " --show-annotation android.annotation.SystemApi\\(" + "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES" + + "\\)" + + " --show-for-stub-purposes-annotation android.annotation.SystemApi\\(" + + "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS" + "\\) " mainline_service_stubs_args = diff --git a/api/current.txt b/api/current.txt index fea1f2f4cac4..f1338f07f294 100644 --- a/api/current.txt +++ b/api/current.txt @@ -45143,7 +45143,7 @@ package android.telephony { public abstract class CellLocation { ctor public CellLocation(); method public static android.telephony.CellLocation getEmpty(); - method public static void requestLocationUpdate(); + method @Deprecated public static void requestLocationUpdate(); } public abstract class CellSignalStrength { diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt index 355fa18ca249..283af068a077 100644 --- a/api/module-lib-current.txt +++ b/api/module-lib-current.txt @@ -11,24 +11,6 @@ package android.annotation { package android.net { - public final class TetheredClient implements android.os.Parcelable { - ctor public TetheredClient(@NonNull android.net.MacAddress, @NonNull java.util.Collection<android.net.TetheredClient.AddressInfo>, int); - method public int describeContents(); - method @NonNull public java.util.List<android.net.TetheredClient.AddressInfo> getAddresses(); - method @NonNull public android.net.MacAddress getMacAddress(); - method public int getTetheringType(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheredClient> CREATOR; - } - - public static final class TetheredClient.AddressInfo implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public android.net.LinkAddress getAddress(); - method @Nullable public String getHostname(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheredClient.AddressInfo> CREATOR; - } - public final class TetheringConstants { field public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType"; field public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback"; @@ -48,69 +30,15 @@ package android.net { method @NonNull public String[] getTetheringErroredIfaces(); method public boolean isTetheringSupported(); method public boolean isTetheringSupported(@NonNull String); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheringEventCallback); - method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener); method public void requestLatestTetheringEntitlementResult(int, @NonNull android.os.ResultReceiver, boolean); method @Deprecated public int setUsbTethering(boolean); - method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(@NonNull android.net.TetheringManager.TetheringRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback); - method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(int, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback); - method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void stopAllTethering(); - method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.WRITE_SETTINGS}) public void stopTethering(int); + method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(int, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback); method @Deprecated public int tether(@NonNull String); - method @RequiresPermission(anyOf={"android.permission.TETHER_PRIVILEGED", android.Manifest.permission.ACCESS_NETWORK_STATE}) public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback); method @Deprecated public int untether(@NonNull String); - field public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED"; - field public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY"; - field public static final String EXTRA_ACTIVE_TETHER = "tetherArray"; - field public static final String EXTRA_AVAILABLE_TETHER = "availableArray"; - field public static final String EXTRA_ERRORED_TETHER = "erroredArray"; - field public static final int TETHERING_BLUETOOTH = 2; // 0x2 - field public static final int TETHERING_ETHERNET = 5; // 0x5 - field public static final int TETHERING_INVALID = -1; // 0xffffffff - field public static final int TETHERING_NCM = 4; // 0x4 - field public static final int TETHERING_USB = 1; // 0x1 - field public static final int TETHERING_WIFI = 0; // 0x0 - field public static final int TETHERING_WIFI_P2P = 3; // 0x3 - field public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; // 0xc - field public static final int TETHER_ERROR_DISABLE_FORWARDING_ERROR = 9; // 0x9 - field public static final int TETHER_ERROR_ENABLE_FORWARDING_ERROR = 8; // 0x8 - field public static final int TETHER_ERROR_ENTITLEMENT_UNKNOWN = 13; // 0xd - field public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10; // 0xa - field public static final int TETHER_ERROR_INTERNAL_ERROR = 5; // 0x5 - field public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15; // 0xf - field public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14; // 0xe - field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0 - field public static final int TETHER_ERROR_PROVISIONING_FAILED = 11; // 0xb - field public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2; // 0x2 - field public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6; // 0x6 - field public static final int TETHER_ERROR_UNAVAIL_IFACE = 4; // 0x4 - field public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; // 0x1 - field public static final int TETHER_ERROR_UNKNOWN_TYPE = 16; // 0x10 - field public static final int TETHER_ERROR_UNSUPPORTED = 3; // 0x3 - field public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; // 0x7 - field public static final int TETHER_HARDWARE_OFFLOAD_FAILED = 2; // 0x2 - field public static final int TETHER_HARDWARE_OFFLOAD_STARTED = 1; // 0x1 - field public static final int TETHER_HARDWARE_OFFLOAD_STOPPED = 0; // 0x0 - } - - public static interface TetheringManager.OnTetheringEntitlementResultListener { - method public void onTetheringEntitlementResult(int); - } - - public static interface TetheringManager.StartTetheringCallback { - method public default void onTetheringFailed(int); - method public default void onTetheringStarted(); } public static interface TetheringManager.TetheringEventCallback { - method public default void onClientsChanged(@NonNull java.util.Collection<android.net.TetheredClient>); - method public default void onError(@NonNull String, int); - method public default void onOffloadStatusChanged(int); method public default void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps); - method public default void onTetherableInterfacesChanged(@NonNull java.util.List<java.lang.String>); - method public default void onTetheredInterfacesChanged(@NonNull java.util.List<java.lang.String>); - method public default void onTetheringSupported(boolean); - method public default void onUpstreamChanged(@Nullable android.net.Network); } public static class TetheringManager.TetheringInterfaceRegexps { @@ -119,21 +47,5 @@ package android.net { method @NonNull public java.util.List<java.lang.String> getTetherableWifiRegexs(); } - public static class TetheringManager.TetheringRequest { - method @Nullable public android.net.LinkAddress getClientStaticIpv4Address(); - method @Nullable public android.net.LinkAddress getLocalIpv4Address(); - method public boolean getShouldShowEntitlementUi(); - method public int getTetheringType(); - method public boolean isExemptFromEntitlementCheck(); - } - - public static class TetheringManager.TetheringRequest.Builder { - ctor public TetheringManager.TetheringRequest.Builder(int); - method @NonNull public android.net.TetheringManager.TetheringRequest build(); - method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean); - method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setShouldShowEntitlementUi(boolean); - method @NonNull @RequiresPermission("android.permission.TETHER_PRIVILEGED") public android.net.TetheringManager.TetheringRequest.Builder setStaticIpv4Addresses(@NonNull android.net.LinkAddress, @NonNull android.net.LinkAddress); - } - } diff --git a/core/java/android/annotation/SystemApi.java b/core/java/android/annotation/SystemApi.java index 4ac00983af13..a468439c8e74 100644 --- a/core/java/android/annotation/SystemApi.java +++ b/core/java/android/annotation/SystemApi.java @@ -23,7 +23,6 @@ import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.ElementType.PACKAGE; import static java.lang.annotation.ElementType.TYPE; -import java.lang.annotation.Repeatable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -41,7 +40,6 @@ import java.lang.annotation.Target; */ @Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE}) @Retention(RetentionPolicy.RUNTIME) -@Repeatable(SystemApi.Container.class) // TODO(b/146727827): make this non-repeatable public @interface SystemApi { enum Client { /** diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java index f962ea0966bd..47bd207fee65 100644 --- a/core/java/android/bluetooth/BluetoothAdapter.java +++ b/core/java/android/bluetooth/BluetoothAdapter.java @@ -1784,6 +1784,7 @@ public final class BluetoothAdapter { try { mServiceLock.readLock().lock(); if (mService != null) { + if (DBG) Log.d(TAG, "removeActiveDevice, profiles: " + profiles); return mService.removeActiveDevice(profiles); } } catch (RemoteException e) { @@ -1828,6 +1829,9 @@ public final class BluetoothAdapter { try { mServiceLock.readLock().lock(); if (mService != null) { + if (DBG) { + Log.d(TAG, "setActiveDevice, device: " + device + ", profiles: " + profiles); + } return mService.setActiveDevice(device, profiles); } } catch (RemoteException e) { diff --git a/core/java/android/bluetooth/BluetoothMapClient.java b/core/java/android/bluetooth/BluetoothMapClient.java index 4f5c4feb3684..df11d3adac01 100644 --- a/core/java/android/bluetooth/BluetoothMapClient.java +++ b/core/java/android/bluetooth/BluetoothMapClient.java @@ -52,6 +52,18 @@ public final class BluetoothMapClient implements BluetoothProfile { public static final String ACTION_MESSAGE_DELIVERED_SUCCESSFULLY = "android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY"; + /** + * Action to notify read status changed + */ + public static final String ACTION_MESSAGE_READ_STATUS_CHANGED = + "android.bluetooth.mapmce.profile.action.MESSAGE_READ_STATUS_CHANGED"; + + /** + * Action to notify deleted status changed + */ + public static final String ACTION_MESSAGE_DELETED_STATUS_CHANGED = + "android.bluetooth.mapmce.profile.action.MESSAGE_DELETED_STATUS_CHANGED"; + /* Extras used in ACTION_MESSAGE_RECEIVED intent. * NOTE: HANDLE is only valid for a single session with the device. */ public static final String EXTRA_MESSAGE_HANDLE = @@ -65,6 +77,25 @@ public final class BluetoothMapClient implements BluetoothProfile { public static final String EXTRA_SENDER_CONTACT_NAME = "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_NAME"; + /** + * Used as a boolean extra in ACTION_MESSAGE_DELETED_STATUS_CHANGED + * Contains the MAP message deleted status + * Possible values are: + * true: deleted + * false: undeleted + */ + public static final String EXTRA_MESSAGE_DELETED_STATUS = + "android.bluetooth.mapmce.profile.extra.MESSAGE_DELETED_STATUS"; + + /** + * Extra used in ACTION_MESSAGE_READ_STATUS_CHANGED or ACTION_MESSAGE_DELETED_STATUS_CHANGED + * Possible values are: + * 0: failure + * 1: success + */ + public static final String EXTRA_RESULT_CODE = + "android.bluetooth.device.extra.RESULT_CODE"; + /** There was an error trying to obtain the state */ public static final int STATE_ERROR = -1; @@ -75,6 +106,12 @@ public final class BluetoothMapClient implements BluetoothProfile { private static final int UPLOADING_FEATURE_BITMASK = 0x08; + /** Parameters in setMessageStatus */ + public static final int UNREAD = 0; + public static final int READ = 1; + public static final int UNDELETED = 2; + public static final int DELETED = 3; + private BluetoothAdapter mAdapter; private final BluetoothProfileConnector<IBluetoothMapClient> mProfileConnector = new BluetoothProfileConnector(this, BluetoothProfile.MAP_CLIENT, @@ -405,6 +442,38 @@ public final class BluetoothMapClient implements BluetoothProfile { return false; } + /** + * Set message status of message on MSE + * <p> + * When read status changed, the result will be published via + * {@link #ACTION_MESSAGE_READ_STATUS_CHANGED} + * When deleted status changed, the result will be published via + * {@link #ACTION_MESSAGE_DELETED_STATUS_CHANGED} + * + * @param device Bluetooth device + * @param handle message handle + * @param status <code>UNREAD</code> for "unread", <code>READ</code> for + * "read", <code>UNDELETED</code> for "undeleted", <code>DELETED</code> for + * "deleted", otherwise return error + * @return <code>true</code> if request has been sent, <code>false</code> on error + * + */ + @RequiresPermission(Manifest.permission.READ_SMS) + public boolean setMessageStatus(BluetoothDevice device, String handle, int status) { + if (DBG) Log.d(TAG, "setMessageStatus(" + device + ", " + handle + ", " + status + ")"); + final IBluetoothMapClient service = getService(); + if (service != null && isEnabled() && isValidDevice(device) && handle != null && + (status == READ || status == UNREAD || status == UNDELETED || status == DELETED)) { + try { + return service.setMessageStatus(device, handle, status); + } catch (RemoteException e) { + Log.e(TAG, Log.getStackTraceString(new Throwable())); + return false; + } + } + return false; + } + private boolean isEnabled() { BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true; diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java index 0e10c42e61db..0eb3c1e8ad01 100644 --- a/core/java/android/net/MacAddress.java +++ b/core/java/android/net/MacAddress.java @@ -38,7 +38,9 @@ import java.util.Arrays; * Representation of a MAC address. * * This class only supports 48 bits long addresses and does not support 64 bits long addresses. - * Instances of this class are immutable. + * Instances of this class are immutable. This class provides implementations of hashCode() + * and equals() that make it suitable for use as keys in standard implementations of + * {@link java.util.Map}. */ public final class MacAddress implements Parcelable { @@ -122,12 +124,22 @@ public final class MacAddress implements Parcelable { } /** + * Convert this MacAddress to a byte array. + * + * The returned array is in network order. For example, if this MacAddress is 1:2:3:4:5:6, + * the returned array is [1, 2, 3, 4, 5, 6]. + * * @return a byte array representation of this MacAddress. */ public @NonNull byte[] toByteArray() { return byteAddrFromLongAddr(mAddr); } + /** + * Returns a human-readable representation of this MacAddress. + * The exact format is implementation-dependent and should not be assumed to have any + * particular format. + */ @Override public @NonNull String toString() { return stringAddrFromLongAddr(mAddr); diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java index cb9463a59d34..4b806e714ffe 100644 --- a/core/java/android/net/NetworkTemplate.java +++ b/core/java/android/net/NetworkTemplate.java @@ -84,6 +84,15 @@ public class NetworkTemplate implements Parcelable { * @hide */ public static final int NETWORK_TYPE_ALL = -1; + /** + * Virtual RAT type to represent 5G NSA (Non Stand Alone) mode, where the primary cell is + * still LTE and network allocates a secondary 5G cell so telephony reports RAT = LTE along + * with NR state as connected. This should not be overlapped with any of the + * {@code TelephonyManager.NETWORK_TYPE_*} constants. + * + * @hide + */ + public static final int NETWORK_TYPE_5G_NSA = -2; private static boolean isKnownMatchRule(final int rule) { switch (rule) { @@ -472,6 +481,9 @@ public class NetworkTemplate implements Parcelable { return TelephonyManager.NETWORK_TYPE_LTE; case TelephonyManager.NETWORK_TYPE_NR: return TelephonyManager.NETWORK_TYPE_NR; + // Virtual RAT type for 5G NSA mode, see {@link NetworkTemplate#NETWORK_TYPE_5G_NSA}. + case NetworkTemplate.NETWORK_TYPE_5G_NSA: + return NetworkTemplate.NETWORK_TYPE_5G_NSA; default: return TelephonyManager.NETWORK_TYPE_UNKNOWN; } diff --git a/core/java/android/os/HwBinder.java b/core/java/android/os/HwBinder.java index 7c42c36e7747..64ab1d711765 100644 --- a/core/java/android/os/HwBinder.java +++ b/core/java/android/os/HwBinder.java @@ -96,6 +96,15 @@ public abstract class HwBinder implements IHwBinder { throws RemoteException, NoSuchElementException; /** + * This allows getService to bypass the VINTF manifest for testing only. + * + * Disabled on user builds. + * @hide + */ + public static native final void setTrebleTestingOverride( + boolean testingOverride); + + /** * Configures how many threads the process-wide hwbinder threadpool * has to process incoming requests. * diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index a95fe3c4043d..7946dda3170d 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -1331,8 +1331,7 @@ public final class Telephony { Object[] messages; try { messages = (Object[]) intent.getSerializableExtra("pdus"); - } - catch (ClassCastException e) { + } catch (ClassCastException e) { Rlog.e(TAG, "getMessagesFromIntent: " + e); return null; } @@ -1344,9 +1343,12 @@ public final class Telephony { String format = intent.getStringExtra("format"); int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, - SubscriptionManager.getDefaultSmsSubscriptionId()); - - Rlog.v(TAG, " getMessagesFromIntent sub_id : " + subId); + SubscriptionManager.INVALID_SUBSCRIPTION_ID); + if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) { + Rlog.v(TAG, "getMessagesFromIntent with valid subId : " + subId); + } else { + Rlog.v(TAG, "getMessagesFromIntent"); + } int pduCount = messages.length; SmsMessage[] msgs = new SmsMessage[pduCount]; diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java index 0863a813543c..683c74737a2d 100755 --- a/core/java/android/text/format/DateFormat.java +++ b/core/java/android/text/format/DateFormat.java @@ -19,12 +19,12 @@ package android.text.format; import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; +import android.icu.text.DateTimePatternGenerator; import android.provider.Settings; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.SpannedString; -import libcore.icu.ICU; import libcore.icu.LocaleData; import java.text.SimpleDateFormat; @@ -251,7 +251,8 @@ public class DateFormat { * @return a string pattern suitable for use with {@link java.text.SimpleDateFormat}. */ public static String getBestDateTimePattern(Locale locale, String skeleton) { - return ICU.getBestDateTimePattern(skeleton, locale); + DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance(locale); + return dtpg.getBestPattern(skeleton); } /** @@ -333,7 +334,52 @@ public class DateFormat { * order returned here. */ public static char[] getDateFormatOrder(Context context) { - return ICU.getDateFormatOrder(getDateFormatString(context)); + return getDateFormatOrder(getDateFormatString(context)); + } + + /** + * @hide Used by internal framework class {@link android.widget.DatePickerSpinnerDelegate}. + */ + public static char[] getDateFormatOrder(String pattern) { + char[] result = new char[3]; + int resultIndex = 0; + boolean sawDay = false; + boolean sawMonth = false; + boolean sawYear = false; + + for (int i = 0; i < pattern.length(); ++i) { + char ch = pattern.charAt(i); + if (ch == 'd' || ch == 'L' || ch == 'M' || ch == 'y') { + if (ch == 'd' && !sawDay) { + result[resultIndex++] = 'd'; + sawDay = true; + } else if ((ch == 'L' || ch == 'M') && !sawMonth) { + result[resultIndex++] = 'M'; + sawMonth = true; + } else if ((ch == 'y') && !sawYear) { + result[resultIndex++] = 'y'; + sawYear = true; + } + } else if (ch == 'G') { + // Ignore the era specifier, if present. + } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { + throw new IllegalArgumentException("Bad pattern character '" + ch + "' in " + + pattern); + } else if (ch == '\'') { + if (i < pattern.length() - 1 && pattern.charAt(i + 1) == '\'') { + ++i; + } else { + i = pattern.indexOf('\'', i + 1); + if (i == -1) { + throw new IllegalArgumentException("Bad quoting in " + pattern); + } + ++i; + } + } else { + // Ignore spaces and punctuation. + } + } + return result; } private static String getDateFormatString(Context context) { diff --git a/core/java/android/text/format/DateTimeFormat.java b/core/java/android/text/format/DateTimeFormat.java new file mode 100644 index 000000000000..064d7172c44f --- /dev/null +++ b/core/java/android/text/format/DateTimeFormat.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2015 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.text.format; + +import android.icu.text.DateFormat; +import android.icu.text.DateTimePatternGenerator; +import android.icu.text.DisplayContext; +import android.icu.text.SimpleDateFormat; +import android.icu.util.Calendar; +import android.icu.util.ULocale; +import android.util.LruCache; + +/** + * A formatter that outputs a single date/time. + * + * @hide + */ +class DateTimeFormat { + private static final FormatterCache CACHED_FORMATTERS = new FormatterCache(); + + static class FormatterCache extends LruCache<String, DateFormat> { + FormatterCache() { + super(8); + } + } + + private DateTimeFormat() { + } + + public static String format(ULocale icuLocale, Calendar time, int flags, + DisplayContext displayContext) { + String skeleton = DateUtilsBridge.toSkeleton(time, flags); + String key = skeleton + "\t" + icuLocale + "\t" + time.getTimeZone(); + synchronized (CACHED_FORMATTERS) { + DateFormat formatter = CACHED_FORMATTERS.get(key); + if (formatter == null) { + DateTimePatternGenerator generator = DateTimePatternGenerator.getInstance( + icuLocale); + formatter = new SimpleDateFormat(generator.getBestPattern(skeleton), icuLocale); + CACHED_FORMATTERS.put(key, formatter); + } + formatter.setContext(displayContext); + return formatter.format(time); + } + } +} diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java index f236f19e973f..b0253a0af7a7 100644 --- a/core/java/android/text/format/DateUtils.java +++ b/core/java/android/text/format/DateUtils.java @@ -29,7 +29,6 @@ import com.android.internal.R; import libcore.icu.DateIntervalFormat; import libcore.icu.LocaleData; -import libcore.icu.RelativeDateTimeFormatter; import java.io.IOException; import java.util.Calendar; diff --git a/core/java/android/text/format/DateUtilsBridge.java b/core/java/android/text/format/DateUtilsBridge.java new file mode 100644 index 000000000000..370d999abf3e --- /dev/null +++ b/core/java/android/text/format/DateUtilsBridge.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2015 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.text.format; + +import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; + +import android.icu.util.Calendar; +import android.icu.util.GregorianCalendar; +import android.icu.util.TimeZone; +import android.icu.util.ULocale; + +import com.android.internal.annotations.VisibleForTesting; + +/** + * Common methods and constants for the various ICU formatters used to support {@link + * android.text.format.DateUtils}. + * + * @hide + */ +@VisibleForTesting(visibility = PACKAGE) +public final class DateUtilsBridge { + // These are all public API in DateUtils. There are others, but they're either for use with + // other methods (like FORMAT_ABBREV_RELATIVE), don't internationalize (like FORMAT_CAP_AMPM), + // or have never been implemented anyway. + public static final int FORMAT_SHOW_TIME = 0x00001; + public static final int FORMAT_SHOW_WEEKDAY = 0x00002; + public static final int FORMAT_SHOW_YEAR = 0x00004; + public static final int FORMAT_NO_YEAR = 0x00008; + public static final int FORMAT_SHOW_DATE = 0x00010; + public static final int FORMAT_NO_MONTH_DAY = 0x00020; + public static final int FORMAT_12HOUR = 0x00040; + public static final int FORMAT_24HOUR = 0x00080; + public static final int FORMAT_UTC = 0x02000; + public static final int FORMAT_ABBREV_TIME = 0x04000; + public static final int FORMAT_ABBREV_WEEKDAY = 0x08000; + public static final int FORMAT_ABBREV_MONTH = 0x10000; + public static final int FORMAT_NUMERIC_DATE = 0x20000; + public static final int FORMAT_ABBREV_RELATIVE = 0x40000; + public static final int FORMAT_ABBREV_ALL = 0x80000; + + /** + * Creates an immutable ICU timezone backed by the specified libcore timezone data. At the time + * of writing the libcore implementation is faster but restricted to 1902 - 2038. Callers must + * not modify the {@code tz} after calling this method. + */ + public static TimeZone icuTimeZone(java.util.TimeZone tz) { + TimeZone icuTimeZone = TimeZone.getTimeZone(tz.getID()); + icuTimeZone.freeze(); // Optimization - allows the timezone to be copied cheaply. + return icuTimeZone; + } + + /** + * Create a GregorianCalendar based on the arguments + */ + public static Calendar createIcuCalendar(TimeZone icuTimeZone, ULocale icuLocale, + long timeInMillis) { + Calendar calendar = new GregorianCalendar(icuTimeZone, icuLocale); + calendar.setTimeInMillis(timeInMillis); + return calendar; + } + + public static String toSkeleton(Calendar calendar, int flags) { + return toSkeleton(calendar, calendar, flags); + } + + public static String toSkeleton(Calendar startCalendar, Calendar endCalendar, int flags) { + if ((flags & FORMAT_ABBREV_ALL) != 0) { + flags |= FORMAT_ABBREV_MONTH | FORMAT_ABBREV_TIME | FORMAT_ABBREV_WEEKDAY; + } + + String monthPart = "MMMM"; + if ((flags & FORMAT_NUMERIC_DATE) != 0) { + monthPart = "M"; + } else if ((flags & FORMAT_ABBREV_MONTH) != 0) { + monthPart = "MMM"; + } + + String weekPart = "EEEE"; + if ((flags & FORMAT_ABBREV_WEEKDAY) != 0) { + weekPart = "EEE"; + } + + String timePart = "j"; // "j" means choose 12 or 24 hour based on current locale. + if ((flags & FORMAT_24HOUR) != 0) { + timePart = "H"; + } else if ((flags & FORMAT_12HOUR) != 0) { + timePart = "h"; + } + + // If we've not been asked to abbreviate times, or we're using the 24-hour clock (where it + // never makes sense to leave out the minutes), include minutes. This gets us times like + // "4 PM" while avoiding times like "16" (for "16:00"). + if ((flags & FORMAT_ABBREV_TIME) == 0 || (flags & FORMAT_24HOUR) != 0) { + timePart += "m"; + } else { + // Otherwise, we're abbreviating a 12-hour time, and should only show the minutes + // if they're not both "00". + if (!(onTheHour(startCalendar) && onTheHour(endCalendar))) { + timePart = timePart + "m"; + } + } + + if (fallOnDifferentDates(startCalendar, endCalendar)) { + flags |= FORMAT_SHOW_DATE; + } + + if (fallInSameMonth(startCalendar, endCalendar) && (flags & FORMAT_NO_MONTH_DAY) != 0) { + flags &= (~FORMAT_SHOW_WEEKDAY); + flags &= (~FORMAT_SHOW_TIME); + } + + if ((flags & (FORMAT_SHOW_DATE | FORMAT_SHOW_TIME | FORMAT_SHOW_WEEKDAY)) == 0) { + flags |= FORMAT_SHOW_DATE; + } + + // If we've been asked to show the date, work out whether we think we should show the year. + if ((flags & FORMAT_SHOW_DATE) != 0) { + if ((flags & FORMAT_SHOW_YEAR) != 0) { + // The caller explicitly wants us to show the year. + } else if ((flags & FORMAT_NO_YEAR) != 0) { + // The caller explicitly doesn't want us to show the year, even if we otherwise + // would. + } else if (!fallInSameYear(startCalendar, endCalendar) || !isThisYear(startCalendar)) { + flags |= FORMAT_SHOW_YEAR; + } + } + + StringBuilder builder = new StringBuilder(); + if ((flags & (FORMAT_SHOW_DATE | FORMAT_NO_MONTH_DAY)) != 0) { + if ((flags & FORMAT_SHOW_YEAR) != 0) { + builder.append("y"); + } + builder.append(monthPart); + if ((flags & FORMAT_NO_MONTH_DAY) == 0) { + builder.append("d"); + } + } + if ((flags & FORMAT_SHOW_WEEKDAY) != 0) { + builder.append(weekPart); + } + if ((flags & FORMAT_SHOW_TIME) != 0) { + builder.append(timePart); + } + return builder.toString(); + } + + public static int dayDistance(Calendar c1, Calendar c2) { + return c2.get(Calendar.JULIAN_DAY) - c1.get(Calendar.JULIAN_DAY); + } + + /** + * Returns whether the argument will be displayed as if it were midnight, using any of the + * skeletons provided by {@link #toSkeleton}. + */ + public static boolean isDisplayMidnightUsingSkeleton(Calendar c) { + // All the skeletons returned by toSkeleton have minute precision (they may abbreviate + // 4:00 PM to 4 PM but will still show the following minute as 4:01 PM). + return c.get(Calendar.HOUR_OF_DAY) == 0 && c.get(Calendar.MINUTE) == 0; + } + + private static boolean onTheHour(Calendar c) { + return c.get(Calendar.MINUTE) == 0 && c.get(Calendar.SECOND) == 0; + } + + private static boolean fallOnDifferentDates(Calendar c1, Calendar c2) { + return c1.get(Calendar.YEAR) != c2.get(Calendar.YEAR) + || c1.get(Calendar.MONTH) != c2.get(Calendar.MONTH) + || c1.get(Calendar.DAY_OF_MONTH) != c2.get(Calendar.DAY_OF_MONTH); + } + + private static boolean fallInSameMonth(Calendar c1, Calendar c2) { + return c1.get(Calendar.MONTH) == c2.get(Calendar.MONTH); + } + + private static boolean fallInSameYear(Calendar c1, Calendar c2) { + return c1.get(Calendar.YEAR) == c2.get(Calendar.YEAR); + } + + private static boolean isThisYear(Calendar c) { + Calendar now = (Calendar) c.clone(); + now.setTimeInMillis(System.currentTimeMillis()); + return c.get(Calendar.YEAR) == now.get(Calendar.YEAR); + } +} diff --git a/core/java/android/text/format/RelativeDateTimeFormatter.java b/core/java/android/text/format/RelativeDateTimeFormatter.java new file mode 100644 index 000000000000..c5bca172873a --- /dev/null +++ b/core/java/android/text/format/RelativeDateTimeFormatter.java @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2015 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.text.format; + +import static android.text.format.DateUtilsBridge.FORMAT_ABBREV_ALL; +import static android.text.format.DateUtilsBridge.FORMAT_ABBREV_MONTH; +import static android.text.format.DateUtilsBridge.FORMAT_ABBREV_RELATIVE; +import static android.text.format.DateUtilsBridge.FORMAT_NO_YEAR; +import static android.text.format.DateUtilsBridge.FORMAT_NUMERIC_DATE; +import static android.text.format.DateUtilsBridge.FORMAT_SHOW_DATE; +import static android.text.format.DateUtilsBridge.FORMAT_SHOW_TIME; +import static android.text.format.DateUtilsBridge.FORMAT_SHOW_YEAR; + +import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; + +import android.icu.text.DisplayContext; +import android.icu.util.Calendar; +import android.icu.util.ULocale; +import android.util.LruCache; + +import com.android.internal.annotations.VisibleForTesting; + +import java.util.Locale; + +/** + * Exposes icu4j's RelativeDateTimeFormatter. + * + * @hide + */ +@VisibleForTesting(visibility = PACKAGE) +public final class RelativeDateTimeFormatter { + + public static final long SECOND_IN_MILLIS = 1000; + public static final long MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60; + public static final long HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60; + public static final long DAY_IN_MILLIS = HOUR_IN_MILLIS * 24; + public static final long WEEK_IN_MILLIS = DAY_IN_MILLIS * 7; + // YEAR_IN_MILLIS considers 364 days as a year. However, since this + // constant comes from public API in DateUtils, it cannot be fixed here. + public static final long YEAR_IN_MILLIS = WEEK_IN_MILLIS * 52; + + private static final int DAY_IN_MS = 24 * 60 * 60 * 1000; + private static final int EPOCH_JULIAN_DAY = 2440588; + + private static final FormatterCache CACHED_FORMATTERS = new FormatterCache(); + + static class FormatterCache + extends LruCache<String, android.icu.text.RelativeDateTimeFormatter> { + FormatterCache() { + super(8); + } + } + + private RelativeDateTimeFormatter() { + } + + /** + * This is the internal API that implements the functionality of DateUtils + * .getRelativeTimeSpanString(long, + * long, long, int), which is to return a string describing 'time' as a time relative to 'now' + * such as '5 minutes ago', or 'In 2 days'. More examples can be found in DateUtils' doc. + * <p> + * In the implementation below, it selects the appropriate time unit based on the elapsed time + * between time' and 'now', e.g. minutes, days and etc. Callers may also specify the desired + * minimum resolution to show in the result. For example, '45 minutes ago' will become '0 hours + * ago' when minResolution is HOUR_IN_MILLIS. Once getting the quantity and unit to display, it + * calls icu4j's RelativeDateTimeFormatter to format the actual string according to the given + * locale. + * <p> + * Note that when minResolution is set to DAY_IN_MILLIS, it returns the result depending on the + * actual date difference. For example, it will return 'Yesterday' even if 'time' was less than + * 24 hours ago but falling onto a different calendar day. + * <p> + * It takes two additional parameters of Locale and TimeZone than the DateUtils' API. Caller + * must specify the locale and timezone. FORMAT_ABBREV_RELATIVE or FORMAT_ABBREV_ALL can be set + * in 'flags' to get the abbreviated forms when available. When 'time' equals to 'now', it + * always // returns a string like '0 seconds/minutes/... ago' according to minResolution. + */ + public static String getRelativeTimeSpanString(Locale locale, java.util.TimeZone tz, long time, + long now, long minResolution, int flags) { + // Android has been inconsistent about capitalization in the past. e.g. bug + // http://b/20247811. + // Now we capitalize everything consistently. + final DisplayContext displayContext = + DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE; + return getRelativeTimeSpanString(locale, tz, time, now, minResolution, flags, + displayContext); + } + + public static String getRelativeTimeSpanString(Locale locale, java.util.TimeZone tz, long time, + long now, long minResolution, int flags, DisplayContext displayContext) { + if (locale == null) { + throw new NullPointerException("locale == null"); + } + if (tz == null) { + throw new NullPointerException("tz == null"); + } + ULocale icuLocale = ULocale.forLocale(locale); + android.icu.util.TimeZone icuTimeZone = DateUtilsBridge.icuTimeZone(tz); + return getRelativeTimeSpanString(icuLocale, icuTimeZone, time, now, minResolution, flags, + displayContext); + } + + private static String getRelativeTimeSpanString(ULocale icuLocale, + android.icu.util.TimeZone icuTimeZone, long time, long now, long minResolution, + int flags, + DisplayContext displayContext) { + + long duration = Math.abs(now - time); + boolean past = (now >= time); + + android.icu.text.RelativeDateTimeFormatter.Style style; + if ((flags & (FORMAT_ABBREV_RELATIVE | FORMAT_ABBREV_ALL)) != 0) { + style = android.icu.text.RelativeDateTimeFormatter.Style.SHORT; + } else { + style = android.icu.text.RelativeDateTimeFormatter.Style.LONG; + } + + android.icu.text.RelativeDateTimeFormatter.Direction direction; + if (past) { + direction = android.icu.text.RelativeDateTimeFormatter.Direction.LAST; + } else { + direction = android.icu.text.RelativeDateTimeFormatter.Direction.NEXT; + } + + // 'relative' defaults to true as we are generating relative time span + // string. It will be set to false when we try to display strings without + // a quantity, such as 'Yesterday', etc. + boolean relative = true; + int count; + android.icu.text.RelativeDateTimeFormatter.RelativeUnit unit; + android.icu.text.RelativeDateTimeFormatter.AbsoluteUnit aunit = null; + + if (duration < MINUTE_IN_MILLIS && minResolution < MINUTE_IN_MILLIS) { + count = (int) (duration / SECOND_IN_MILLIS); + unit = android.icu.text.RelativeDateTimeFormatter.RelativeUnit.SECONDS; + } else if (duration < HOUR_IN_MILLIS && minResolution < HOUR_IN_MILLIS) { + count = (int) (duration / MINUTE_IN_MILLIS); + unit = android.icu.text.RelativeDateTimeFormatter.RelativeUnit.MINUTES; + } else if (duration < DAY_IN_MILLIS && minResolution < DAY_IN_MILLIS) { + // Even if 'time' actually happened yesterday, we don't format it as + // "Yesterday" in this case. Unless the duration is longer than a day, + // or minResolution is specified as DAY_IN_MILLIS by user. + count = (int) (duration / HOUR_IN_MILLIS); + unit = android.icu.text.RelativeDateTimeFormatter.RelativeUnit.HOURS; + } else if (duration < WEEK_IN_MILLIS && minResolution < WEEK_IN_MILLIS) { + count = Math.abs(dayDistance(icuTimeZone, time, now)); + unit = android.icu.text.RelativeDateTimeFormatter.RelativeUnit.DAYS; + + if (count == 2) { + // Some locales have special terms for "2 days ago". Return them if + // available. Note that we cannot set up direction and unit here and + // make it fall through to use the call near the end of the function, + // because for locales that don't have special terms for "2 days ago", + // icu4j returns an empty string instead of falling back to strings + // like "2 days ago". + String str; + if (past) { + synchronized (CACHED_FORMATTERS) { + str = getFormatter(icuLocale, style, displayContext).format( + android.icu.text.RelativeDateTimeFormatter.Direction.LAST_2, + android.icu.text.RelativeDateTimeFormatter.AbsoluteUnit.DAY); + } + } else { + synchronized (CACHED_FORMATTERS) { + str = getFormatter(icuLocale, style, displayContext).format( + android.icu.text.RelativeDateTimeFormatter.Direction.NEXT_2, + android.icu.text.RelativeDateTimeFormatter.AbsoluteUnit.DAY); + } + } + if (str != null && !str.isEmpty()) { + return str; + } + // Fall back to show something like "2 days ago". + } else if (count == 1) { + // Show "Yesterday / Tomorrow" instead of "1 day ago / In 1 day". + aunit = android.icu.text.RelativeDateTimeFormatter.AbsoluteUnit.DAY; + relative = false; + } else if (count == 0) { + // Show "Today" if time and now are on the same day. + aunit = android.icu.text.RelativeDateTimeFormatter.AbsoluteUnit.DAY; + direction = android.icu.text.RelativeDateTimeFormatter.Direction.THIS; + relative = false; + } + } else if (minResolution == WEEK_IN_MILLIS) { + count = (int) (duration / WEEK_IN_MILLIS); + unit = android.icu.text.RelativeDateTimeFormatter.RelativeUnit.WEEKS; + } else { + Calendar timeCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, time); + // The duration is longer than a week and minResolution is not + // WEEK_IN_MILLIS. Return the absolute date instead of relative time. + + // Bug 19822016: + // If user doesn't supply the year display flag, we need to explicitly + // set that to show / hide the year based on time and now. Otherwise + // formatDateRange() would determine that based on the current system + // time and may give wrong results. + if ((flags & (FORMAT_NO_YEAR | FORMAT_SHOW_YEAR)) == 0) { + Calendar nowCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, + now); + + if (timeCalendar.get(Calendar.YEAR) != nowCalendar.get(Calendar.YEAR)) { + flags |= FORMAT_SHOW_YEAR; + } else { + flags |= FORMAT_NO_YEAR; + } + } + return DateTimeFormat.format(icuLocale, timeCalendar, flags, displayContext); + } + + synchronized (CACHED_FORMATTERS) { + android.icu.text.RelativeDateTimeFormatter formatter = + getFormatter(icuLocale, style, displayContext); + if (relative) { + return formatter.format(count, direction, unit); + } else { + return formatter.format(direction, aunit); + } + } + } + + /** + * This is the internal API that implements DateUtils.getRelativeDateTimeString(long, long, + * long, long, int), which is to return a string describing 'time' as a time relative to 'now', + * formatted like '[relative time/date], [time]'. More examples can be found in DateUtils' doc. + * <p> + * The function is similar to getRelativeTimeSpanString, but it always appends the absolute time + * to the relative time string to return '[relative time/date clause], [absolute time clause]'. + * It also takes an extra parameter transitionResolution to determine the format of the date + * clause. When the elapsed time is less than the transition resolution, it displays the + * relative time string. Otherwise, it gives the absolute numeric date string as the date + * clause. With the date and time clauses, it relies on icu4j's + * RelativeDateTimeFormatter::combineDateAndTime() + * to concatenate the two. + * <p> + * It takes two additional parameters of Locale and TimeZone than the DateUtils' API. Caller + * must specify the locale and timezone. FORMAT_ABBREV_RELATIVE or FORMAT_ABBREV_ALL can be set + * in 'flags' to get the abbreviated forms when they are available. + * <p> + * Bug 5252772: Since the absolute time will always be part of the result, minResolution will be + * set to at least DAY_IN_MILLIS to correctly indicate the date difference. For example, when + * it's 1:30 AM, it will return 'Yesterday, 11:30 PM' for getRelativeDateTimeString(null, null, + * now - 2 hours, now, HOUR_IN_MILLIS, DAY_IN_MILLIS, 0), instead of '2 hours ago, 11:30 PM' + * even with minResolution being HOUR_IN_MILLIS. + */ + public static String getRelativeDateTimeString(Locale locale, java.util.TimeZone tz, long time, + long now, long minResolution, long transitionResolution, int flags) { + + if (locale == null) { + throw new NullPointerException("locale == null"); + } + if (tz == null) { + throw new NullPointerException("tz == null"); + } + ULocale icuLocale = ULocale.forLocale(locale); + android.icu.util.TimeZone icuTimeZone = DateUtilsBridge.icuTimeZone(tz); + + long duration = Math.abs(now - time); + // It doesn't make much sense to have results like: "1 week ago, 10:50 AM". + if (transitionResolution > WEEK_IN_MILLIS) { + transitionResolution = WEEK_IN_MILLIS; + } + android.icu.text.RelativeDateTimeFormatter.Style style; + if ((flags & (FORMAT_ABBREV_RELATIVE | FORMAT_ABBREV_ALL)) != 0) { + style = android.icu.text.RelativeDateTimeFormatter.Style.SHORT; + } else { + style = android.icu.text.RelativeDateTimeFormatter.Style.LONG; + } + + Calendar timeCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, time); + Calendar nowCalendar = DateUtilsBridge.createIcuCalendar(icuTimeZone, icuLocale, now); + + int days = Math.abs(DateUtilsBridge.dayDistance(timeCalendar, nowCalendar)); + + // Now get the date clause, either in relative format or the actual date. + String dateClause; + if (duration < transitionResolution) { + // This is to fix bug 5252772. If there is any date difference, we should + // promote the minResolution to DAY_IN_MILLIS so that it can display the + // date instead of "x hours/minutes ago, [time]". + if (days > 0 && minResolution < DAY_IN_MILLIS) { + minResolution = DAY_IN_MILLIS; + } + dateClause = getRelativeTimeSpanString(icuLocale, icuTimeZone, time, now, minResolution, + flags, DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE); + } else { + // We always use fixed flags to format the date clause. User-supplied + // flags are ignored. + if (timeCalendar.get(Calendar.YEAR) != nowCalendar.get(Calendar.YEAR)) { + // Different years + flags = FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_NUMERIC_DATE; + } else { + // Default + flags = FORMAT_SHOW_DATE | FORMAT_NO_YEAR | FORMAT_ABBREV_MONTH; + } + + dateClause = DateTimeFormat.format(icuLocale, timeCalendar, flags, + DisplayContext.CAPITALIZATION_FOR_BEGINNING_OF_SENTENCE); + } + + String timeClause = DateTimeFormat.format(icuLocale, timeCalendar, FORMAT_SHOW_TIME, + DisplayContext.CAPITALIZATION_NONE); + + // icu4j also has other options available to control the capitalization. We are currently + // using + // the _NONE option only. + DisplayContext capitalizationContext = DisplayContext.CAPITALIZATION_NONE; + + // Combine the two clauses, such as '5 days ago, 10:50 AM'. + synchronized (CACHED_FORMATTERS) { + return getFormatter(icuLocale, style, capitalizationContext) + .combineDateAndTime(dateClause, timeClause); + } + } + + /** + * getFormatter() caches the RelativeDateTimeFormatter instances based on the combination of + * localeName, sytle and capitalizationContext. It should always be used along with the action + * of the formatter in a synchronized block, because otherwise the formatter returned by + * getFormatter() may have been evicted by the time of the call to formatter->action(). + */ + private static android.icu.text.RelativeDateTimeFormatter getFormatter( + ULocale locale, android.icu.text.RelativeDateTimeFormatter.Style style, + DisplayContext displayContext) { + String key = locale + "\t" + style + "\t" + displayContext; + android.icu.text.RelativeDateTimeFormatter formatter = CACHED_FORMATTERS.get(key); + if (formatter == null) { + formatter = android.icu.text.RelativeDateTimeFormatter.getInstance( + locale, null, style, displayContext); + CACHED_FORMATTERS.put(key, formatter); + } + return formatter; + } + + // Return the date difference for the two times in a given timezone. + private static int dayDistance(android.icu.util.TimeZone icuTimeZone, long startTime, + long endTime) { + return julianDay(icuTimeZone, endTime) - julianDay(icuTimeZone, startTime); + } + + private static int julianDay(android.icu.util.TimeZone icuTimeZone, long time) { + long utcMs = time + icuTimeZone.getOffset(time); + return (int) (utcMs / DAY_IN_MS) + EPOCH_JULIAN_DAY; + } +} diff --git a/core/java/android/widget/DatePickerSpinnerDelegate.java b/core/java/android/widget/DatePickerSpinnerDelegate.java index 096e6ea52c8a..fd89b2e09131 100644 --- a/core/java/android/widget/DatePickerSpinnerDelegate.java +++ b/core/java/android/widget/DatePickerSpinnerDelegate.java @@ -34,8 +34,6 @@ import android.view.inputmethod.InputMethodManager; import android.widget.DatePicker.AbstractDatePickerDelegate; import android.widget.NumberPicker.OnValueChangeListener; -import libcore.icu.ICU; - import java.text.DateFormatSymbols; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -459,7 +457,7 @@ class DatePickerSpinnerDelegate extends AbstractDatePickerDelegate { // We use numeric spinners for year and day, but textual months. Ask icu4c what // order the user's locale uses for that combination. http://b/7207103. String pattern = DateFormat.getBestDateTimePattern(Locale.getDefault(), "yyyyMMMdd"); - char[] order = ICU.getDateFormatOrder(pattern); + char[] order = DateFormat.getDateFormatOrder(pattern); final int spinnerCount = order.length; for (int i = 0; i < spinnerCount; i++) { switch (order[i]) { diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java index 9ab30d358425..da2039560306 100644 --- a/core/java/android/widget/SelectionActionModeHelper.java +++ b/core/java/android/widget/SelectionActionModeHelper.java @@ -109,7 +109,7 @@ public final class SelectionActionModeHelper { * * @return the swap result, index 0 is the start index and index 1 is the end index. */ - private static int[] sortSelctionIndices(int selectionStart, int selectionEnd) { + private static int[] sortSelectionIndices(int selectionStart, int selectionEnd) { if (selectionStart < selectionEnd) { return new int[]{selectionStart, selectionEnd}; } @@ -123,11 +123,11 @@ public final class SelectionActionModeHelper { * @param textView the selected TextView. * @return the swap result, index 0 is the start index and index 1 is the end index. */ - private static int[] sortSelctionIndicesFromTextView(TextView textView) { + private static int[] sortSelectionIndicesFromTextView(TextView textView) { int selectionStart = textView.getSelectionStart(); int selectionEnd = textView.getSelectionEnd(); - return sortSelctionIndices(selectionStart, selectionEnd); + return sortSelectionIndices(selectionStart, selectionEnd); } /** @@ -136,7 +136,7 @@ public final class SelectionActionModeHelper { public void startSelectionActionModeAsync(boolean adjustSelection) { // Check if the smart selection should run for editable text. adjustSelection &= getTextClassificationSettings().isSmartSelectionEnabled(); - int[] sortedSelectionIndices = sortSelctionIndicesFromTextView(mTextView); + int[] sortedSelectionIndices = sortSelectionIndicesFromTextView(mTextView); mSelectionTracker.onOriginalSelection( getText(mTextView), @@ -166,7 +166,7 @@ public final class SelectionActionModeHelper { * Starts Link ActionMode. */ public void startLinkActionModeAsync(int start, int end) { - int[] indexResult = sortSelctionIndices(start, end); + int[] indexResult = sortSelectionIndices(start, end); mSelectionTracker.onOriginalSelection(getText(mTextView), indexResult[0], indexResult[1], true /*isLink*/); cancelAsyncTask(); @@ -202,21 +202,21 @@ public final class SelectionActionModeHelper { /** Reports a selection action event. */ public void onSelectionAction(int menuItemId, @Nullable String actionLabel) { - int[] sortedSelectionIndices = sortSelctionIndicesFromTextView(mTextView); + int[] sortedSelectionIndices = sortSelectionIndicesFromTextView(mTextView); mSelectionTracker.onSelectionAction( sortedSelectionIndices[0], sortedSelectionIndices[1], getActionType(menuItemId), actionLabel, mTextClassification); } public void onSelectionDrag() { - int[] sortedSelectionIndices = sortSelctionIndicesFromTextView(mTextView); + int[] sortedSelectionIndices = sortSelectionIndicesFromTextView(mTextView); mSelectionTracker.onSelectionAction( sortedSelectionIndices[0], sortedSelectionIndices[1], SelectionEvent.ACTION_DRAG, /* actionLabel= */ null, mTextClassification); } public void onTextChanged(int start, int end) { - int[] sortedSelectionIndices = sortSelctionIndices(start, end); + int[] sortedSelectionIndices = sortSelectionIndices(start, end); mSelectionTracker.onTextChanged(sortedSelectionIndices[0], sortedSelectionIndices[1], mTextClassification); } @@ -335,7 +335,7 @@ public final class SelectionActionModeHelper { startSelectionActionMode(startSelectionResult); }; // TODO do not trigger the animation if the change included only non-printable characters - int[] sortedSelectionIndices = sortSelctionIndicesFromTextView(mTextView); + int[] sortedSelectionIndices = sortSelectionIndicesFromTextView(mTextView); final boolean didSelectionChange = result != null && (sortedSelectionIndices[0] != result.mStart || sortedSelectionIndices[1] != result.mEnd); @@ -488,7 +488,7 @@ public final class SelectionActionModeHelper { if (actionMode != null) { actionMode.invalidate(); } - final int[] sortedSelectionIndices = sortSelctionIndicesFromTextView(mTextView); + final int[] sortedSelectionIndices = sortSelectionIndicesFromTextView(mTextView); mSelectionTracker.onSelectionUpdated( sortedSelectionIndices[0], sortedSelectionIndices[1], mTextClassification); mTextClassificationAsyncTask = null; @@ -497,7 +497,7 @@ public final class SelectionActionModeHelper { private void resetTextClassificationHelper(int selectionStart, int selectionEnd) { if (selectionStart < 0 || selectionEnd < 0) { // Use selection indices - int[] sortedSelectionIndices = sortSelctionIndicesFromTextView(mTextView); + int[] sortedSelectionIndices = sortSelectionIndicesFromTextView(mTextView); selectionStart = sortedSelectionIndices[0]; selectionEnd = sortedSelectionIndices[1]; } @@ -639,7 +639,7 @@ public final class SelectionActionModeHelper { mAllowReset = false; boolean selected = editor.selectCurrentWord(); if (selected) { - final int[] sortedSelectionIndices = sortSelctionIndicesFromTextView(textView); + final int[] sortedSelectionIndices = sortSelectionIndicesFromTextView(textView); mSelectionStart = sortedSelectionIndices[0]; mSelectionEnd = sortedSelectionIndices[1]; mLogger.logSelectionAction( @@ -1216,7 +1216,7 @@ public final class SelectionActionModeHelper { SelectionResult(int start, int end, @Nullable TextClassification classification, @Nullable TextSelection selection) { - int[] sortedIndices = sortSelctionIndices(start, end); + int[] sortedIndices = sortSelectionIndices(start, end); mStart = sortedIndices[0]; mEnd = sortedIndices[1]; mClassification = classification; diff --git a/core/java/com/android/internal/os/KernelWakelockReader.java b/core/java/com/android/internal/os/KernelWakelockReader.java index cffb0ad9fdb9..3d35d2fbaa82 100644 --- a/core/java/com/android/internal/os/KernelWakelockReader.java +++ b/core/java/com/android/internal/os/KernelWakelockReader.java @@ -153,19 +153,32 @@ public class KernelWakelockReader { } /** + * Attempt to wait for suspend_control service if not immediately available. + */ + private ISuspendControlService waitForSuspendControlService() throws ServiceNotFoundException { + final String name = "suspend_control"; + final int numRetries = 5; + for (int i = 0; i < numRetries; i++) { + mSuspendControlService = ISuspendControlService.Stub.asInterface( + ServiceManager.getService(name)); + if (mSuspendControlService != null) { + return mSuspendControlService; + } + } + throw new ServiceNotFoundException(name); + } + + /** * On success, returns the updated stats from SystemSupend, else returns null. */ private KernelWakelockStats getWakelockStatsFromSystemSuspend( final KernelWakelockStats staleStats) { WakeLockInfo[] wlStats = null; - if (mSuspendControlService == null) { - try { - mSuspendControlService = ISuspendControlService.Stub.asInterface( - ServiceManager.getServiceOrThrow("suspend_control")); - } catch (ServiceNotFoundException e) { - Slog.wtf(TAG, "Required service suspend_control not available", e); - return null; - } + try { + mSuspendControlService = waitForSuspendControlService(); + } catch (ServiceNotFoundException e) { + Slog.wtf(TAG, "Required service suspend_control not available", e); + return null; } try { diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java index 0c2406559dcc..7a79cc9ef868 100644 --- a/core/java/com/android/internal/util/StateMachine.java +++ b/core/java/com/android/internal/util/StateMachine.java @@ -2088,10 +2088,11 @@ public class StateMachine { pw.println(getName() + ":"); pw.println(" total records=" + getLogRecCount()); for (int i = 0; i < getLogRecSize(); i++) { - pw.println(" rec[" + i + "]: " + getLogRec(i).toString()); + pw.println(" rec[" + i + "]: " + getLogRec(i)); pw.flush(); } - pw.println("curState=" + getCurrentState().getName()); + final IState curState = getCurrentState(); + pw.println("curState=" + (curState == null ? "<QUIT>" : curState.getName())); } @Override diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp index b6427c9aa01c..48f33a6a3d77 100644 --- a/core/jni/android_os_HwBinder.cpp +++ b/core/jni/android_os_HwBinder.cpp @@ -339,6 +339,10 @@ static jobject JHwBinder_native_getService( return JHwRemoteBinder::NewObject(env, service); } +void JHwBinder_native_setTrebleTestingOverride(JNIEnv*, jclass, jboolean testingOverride) { + hardware::details::setTrebleTestingOverride(testingOverride); +} + void JHwBinder_native_configureRpcThreadpool(JNIEnv *, jclass, jlong maxThreads, jboolean callerWillJoin) { CHECK(maxThreads > 0); @@ -368,6 +372,9 @@ static JNINativeMethod gMethods[] = { { "getService", "(Ljava/lang/String;Ljava/lang/String;Z)L" PACKAGE_PATH "/IHwBinder;", (void *)JHwBinder_native_getService }, + { "setTrebleTestingOverride", "(Z)V", + (void *)JHwBinder_native_setTrebleTestingOverride }, + { "configureRpcThreadpool", "(JZ)V", (void *)JHwBinder_native_configureRpcThreadpool }, diff --git a/core/jni/android_os_HwParcel.cpp b/core/jni/android_os_HwParcel.cpp index a88f8919ed08..ff336ee64b54 100644 --- a/core/jni/android_os_HwParcel.cpp +++ b/core/jni/android_os_HwParcel.cpp @@ -122,10 +122,18 @@ void signalExceptionForError(JNIEnv *env, status_t err, bool canThrowRemoteExcep std::stringstream ss; ss << "HwBinder Error: (" << err << ")"; - jniThrowException( - env, - canThrowRemoteException ? "android/os/RemoteException" : "java/lang/RuntimeException", - ss.str().c_str()); + const char* exception = nullptr; + if (canThrowRemoteException) { + if (err == DEAD_OBJECT) { + exception = "android/os/DeadObjectException"; + } else { + exception = "android/os/RemoteException"; + } + } else { + exception = "java/lang/RuntimeException"; + } + + jniThrowException(env, exception, ss.str().c_str()); break; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index cc988ff94926..85a224206346 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -228,6 +228,8 @@ <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED" /> <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY" /> <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY" /> + <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_READ_STATUS_CHANGED" /> + <protected-broadcast android:name="android.bluetooth.mapmce.profile.action.MESSAGE_DELETED_STATUS_CHANGED" /> <protected-broadcast android:name="com.android.bluetooth.BluetoothMapContentObserver.action.MESSAGE_SENT" /> <protected-broadcast diff --git a/core/tests/bluetoothtests/AndroidManifest.xml b/core/tests/bluetoothtests/AndroidManifest.xml index 7f9d8749358c..6849a90f5010 100644 --- a/core/tests/bluetoothtests/AndroidManifest.xml +++ b/core/tests/bluetoothtests/AndroidManifest.xml @@ -15,14 +15,18 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.android.bluetooth.tests" > + package="com.android.bluetooth.tests" + android:sharedUserId="android.uid.bluetooth" > <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> + <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" /> <uses-permission android:name="android.permission.BROADCAST_STICKY" /> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> <uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> + <uses-permission android:name="android.permission.RECEIVE_SMS" /> + <uses-permission android:name="android.permission.READ_SMS"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_SETTINGS" /> <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" /> diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java index 4b32ceae0617..89dbe3f75b56 100644 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothStressTest.java @@ -360,6 +360,30 @@ public class BluetoothStressTest extends InstrumentationTestCase { mTestUtils.unpair(mAdapter, device); } + /* Make sure there is at least 1 unread message in the last week on remote device */ + public void testMceSetMessageStatus() { + int iterations = BluetoothTestRunner.sMceSetMessageStatusIterations; + if (iterations == 0) { + return; + } + + BluetoothDevice device = mAdapter.getRemoteDevice(BluetoothTestRunner.sDeviceAddress); + mTestUtils.enable(mAdapter); + mTestUtils.connectProfile(mAdapter, device, BluetoothProfile.MAP_CLIENT, null); + mTestUtils.mceGetUnreadMessage(mAdapter, device); + + for (int i = 0; i < iterations; i++) { + mTestUtils.mceSetMessageStatus(mAdapter, device, BluetoothMapClient.READ); + mTestUtils.mceSetMessageStatus(mAdapter, device, BluetoothMapClient.UNREAD); + } + + /** + * It is hard to find device to support set undeleted status, so just + * set deleted in 1 iteration + **/ + mTestUtils.mceSetMessageStatus(mAdapter, device, BluetoothMapClient.DELETED); + } + private void sleep(long time) { try { Thread.sleep(time); diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestRunner.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestRunner.java index 56e691d8c246..d19c2c3e7e24 100644 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestRunner.java +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestRunner.java @@ -40,6 +40,7 @@ import android.util.Log; * [-e connect_input_iterations <iterations>] \ * [-e connect_pan_iterations <iterations>] \ * [-e start_stop_sco_iterations <iterations>] \ + * [-e mce_set_message_status_iterations <iterations>] \ * [-e pair_address <address>] \ * [-e headset_address <address>] \ * [-e a2dp_address <address>] \ @@ -64,6 +65,7 @@ public class BluetoothTestRunner extends InstrumentationTestRunner { public static int sConnectInputIterations = 100; public static int sConnectPanIterations = 100; public static int sStartStopScoIterations = 100; + public static int sMceSetMessageStatusIterations = 100; public static String sDeviceAddress = ""; public static byte[] sDevicePairPin = {'1', '2', '3', '4'}; @@ -173,6 +175,15 @@ public class BluetoothTestRunner extends InstrumentationTestRunner { } } + val = arguments.getString("mce_set_message_status_iterations"); + if (val != null) { + try { + sMceSetMessageStatusIterations = Integer.parseInt(val); + } catch (NumberFormatException e) { + // Invalid argument, fall back to default value + } + } + val = arguments.getString("device_address"); if (val != null) { sDeviceAddress = val; diff --git a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java index ed613c36b89b..409025bc670d 100644 --- a/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java +++ b/core/tests/bluetoothtests/src/android/bluetooth/BluetoothTestUtils.java @@ -56,6 +56,10 @@ public class BluetoothTestUtils extends Assert { private static final int CONNECT_PROXY_TIMEOUT = 5000; /** Time between polls in ms. */ private static final int POLL_TIME = 100; + /** Timeout to get map message in ms. */ + private static final int GET_UNREAD_MESSAGE_TIMEOUT = 10000; + /** Timeout to set map message status in ms. */ + private static final int SET_MESSAGE_STATUS_TIMEOUT = 2000; private abstract class FlagReceiver extends BroadcastReceiver { private int mExpectedFlags = 0; @@ -98,6 +102,8 @@ public class BluetoothTestUtils extends Assert { private static final int STATE_TURNING_ON_FLAG = 1 << 6; private static final int STATE_ON_FLAG = 1 << 7; private static final int STATE_TURNING_OFF_FLAG = 1 << 8; + private static final int STATE_GET_MESSAGE_FINISHED_FLAG = 1 << 9; + private static final int STATE_SET_MESSAGE_STATUS_FINISHED_FLAG = 1 << 10; public BluetoothReceiver(int expectedFlags) { super(expectedFlags); @@ -231,6 +237,9 @@ public class BluetoothTestUtils extends Assert { case BluetoothProfile.PAN: mConnectionAction = BluetoothPan.ACTION_CONNECTION_STATE_CHANGED; break; + case BluetoothProfile.MAP_CLIENT: + mConnectionAction = BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED; + break; default: mConnectionAction = null; } @@ -308,6 +317,34 @@ public class BluetoothTestUtils extends Assert { } } + + private class MceSetMessageStatusReceiver extends FlagReceiver { + private static final int MESSAGE_RECEIVED_FLAG = 1; + private static final int STATUS_CHANGED_FLAG = 1 << 1; + + public MceSetMessageStatusReceiver(int expectedFlags) { + super(expectedFlags); + } + + @Override + public void onReceive(Context context, Intent intent) { + if (BluetoothMapClient.ACTION_MESSAGE_RECEIVED.equals(intent.getAction())) { + String handle = intent.getStringExtra(BluetoothMapClient.EXTRA_MESSAGE_HANDLE); + assertNotNull(handle); + setFiredFlag(MESSAGE_RECEIVED_FLAG); + mMsgHandle = handle; + } else if (BluetoothMapClient.ACTION_MESSAGE_DELETED_STATUS_CHANGED.equals(intent.getAction())) { + int result = intent.getIntExtra(BluetoothMapClient.EXTRA_RESULT_CODE, BluetoothMapClient.RESULT_FAILURE); + assertEquals(result, BluetoothMapClient.RESULT_SUCCESS); + setFiredFlag(STATUS_CHANGED_FLAG); + } else if (BluetoothMapClient.ACTION_MESSAGE_READ_STATUS_CHANGED.equals(intent.getAction())) { + int result = intent.getIntExtra(BluetoothMapClient.EXTRA_RESULT_CODE, BluetoothMapClient.RESULT_FAILURE); + assertEquals(result, BluetoothMapClient.RESULT_SUCCESS); + setFiredFlag(STATUS_CHANGED_FLAG); + } + } + } + private BluetoothProfile.ServiceListener mServiceListener = new BluetoothProfile.ServiceListener() { @Override @@ -326,6 +363,9 @@ public class BluetoothTestUtils extends Assert { case BluetoothProfile.PAN: mPan = (BluetoothPan) proxy; break; + case BluetoothProfile.MAP_CLIENT: + mMce = (BluetoothMapClient) proxy; + break; } } } @@ -346,6 +386,9 @@ public class BluetoothTestUtils extends Assert { case BluetoothProfile.PAN: mPan = null; break; + case BluetoothProfile.MAP_CLIENT: + mMce = null; + break; } } } @@ -362,6 +405,8 @@ public class BluetoothTestUtils extends Assert { private BluetoothHeadset mHeadset = null; private BluetoothHidHost mInput = null; private BluetoothPan mPan = null; + private BluetoothMapClient mMce = null; + private String mMsgHandle = null; /** * Creates a utility instance for testing Bluetooth. @@ -898,7 +943,7 @@ public class BluetoothTestUtils extends Assert { * @param adapter The BT adapter. * @param device The remote device. * @param profile The profile to connect. One of {@link BluetoothProfile#A2DP}, - * {@link BluetoothProfile#HEADSET}, or {@link BluetoothProfile#HID_HOST}. + * {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#HID_HOST} or {@link BluetoothProfile#MAP_CLIENT}.. * @param methodName The method name to printed in the logs. If null, will be * "connectProfile(profile=<profile>, device=<device>)" */ @@ -941,6 +986,8 @@ public class BluetoothTestUtils extends Assert { assertTrue(((BluetoothHeadset)proxy).connect(device)); } else if (profile == BluetoothProfile.HID_HOST) { assertTrue(((BluetoothHidHost)proxy).connect(device)); + } else if (profile == BluetoothProfile.MAP_CLIENT) { + assertTrue(((BluetoothMapClient)proxy).connect(device)); } break; default: @@ -1016,6 +1063,8 @@ public class BluetoothTestUtils extends Assert { assertTrue(((BluetoothHeadset)proxy).disconnect(device)); } else if (profile == BluetoothProfile.HID_HOST) { assertTrue(((BluetoothHidHost)proxy).disconnect(device)); + } else if (profile == BluetoothProfile.MAP_CLIENT) { + assertTrue(((BluetoothMapClient)proxy).disconnect(device)); } break; case BluetoothProfile.STATE_DISCONNECTED: @@ -1373,6 +1422,89 @@ public class BluetoothTestUtils extends Assert { } } + public void mceGetUnreadMessage(BluetoothAdapter adapter, BluetoothDevice device) { + int mask; + String methodName = "getUnreadMessage"; + + if (!adapter.isEnabled()) { + fail(String.format("%s bluetooth not enabled", methodName)); + } + + if (!adapter.getBondedDevices().contains(device)) { + fail(String.format("%s device not paired", methodName)); + } + + mMce = (BluetoothMapClient) connectProxy(adapter, BluetoothProfile.MAP_CLIENT); + assertNotNull(mMce); + + if (mMce.getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) { + fail(String.format("%s device is not connected", methodName)); + } + + mMsgHandle = null; + mask = MceSetMessageStatusReceiver.MESSAGE_RECEIVED_FLAG; + MceSetMessageStatusReceiver receiver = getMceSetMessageStatusReceiver(device, mask); + assertTrue(mMce.getUnreadMessages(device)); + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < GET_UNREAD_MESSAGE_TIMEOUT) { + if ((receiver.getFiredFlags() & mask) == mask) { + writeOutput(String.format("%s completed", methodName)); + removeReceiver(receiver); + return; + } + sleep(POLL_TIME); + } + int firedFlags = receiver.getFiredFlags(); + removeReceiver(receiver); + fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)", + methodName, mMce.getConnectionState(device), BluetoothMapClient.STATE_CONNECTED, firedFlags, mask)); + } + + /** + * Set a message to read/unread/deleted/undeleted + */ + public void mceSetMessageStatus(BluetoothAdapter adapter, BluetoothDevice device, int status) { + int mask; + String methodName = "setMessageStatus"; + + if (!adapter.isEnabled()) { + fail(String.format("%s bluetooth not enabled", methodName)); + } + + if (!adapter.getBondedDevices().contains(device)) { + fail(String.format("%s device not paired", methodName)); + } + + mMce = (BluetoothMapClient) connectProxy(adapter, BluetoothProfile.MAP_CLIENT); + assertNotNull(mMce); + + if (mMce.getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) { + fail(String.format("%s device is not connected", methodName)); + } + + assertNotNull(mMsgHandle); + mask = MceSetMessageStatusReceiver.STATUS_CHANGED_FLAG; + MceSetMessageStatusReceiver receiver = getMceSetMessageStatusReceiver(device, mask); + + assertTrue(mMce.setMessageStatus(device, mMsgHandle, status)); + + long s = System.currentTimeMillis(); + while (System.currentTimeMillis() - s < SET_MESSAGE_STATUS_TIMEOUT) { + if ((receiver.getFiredFlags() & mask) == mask) { + writeOutput(String.format("%s completed", methodName)); + removeReceiver(receiver); + return; + } + sleep(POLL_TIME); + } + + int firedFlags = receiver.getFiredFlags(); + removeReceiver(receiver); + fail(String.format("%s timeout: state=%d (expected %d), flags=0x%x (expected 0x%s)", + methodName, mMce.getConnectionState(device), BluetoothPan.STATE_CONNECTED, firedFlags, mask)); + } + private void addReceiver(BroadcastReceiver receiver, String[] actions) { IntentFilter filter = new IntentFilter(); for (String action: actions) { @@ -1408,7 +1540,8 @@ public class BluetoothTestUtils extends Assert { String[] actions = { BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED, - BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED}; + BluetoothHidHost.ACTION_CONNECTION_STATE_CHANGED, + BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED}; ConnectProfileReceiver receiver = new ConnectProfileReceiver(device, profile, expectedFlags); addReceiver(receiver, actions); @@ -1430,6 +1563,16 @@ public class BluetoothTestUtils extends Assert { return receiver; } + private MceSetMessageStatusReceiver getMceSetMessageStatusReceiver(BluetoothDevice device, + int expectedFlags) { + String[] actions = {BluetoothMapClient.ACTION_MESSAGE_RECEIVED, + BluetoothMapClient.ACTION_MESSAGE_READ_STATUS_CHANGED, + BluetoothMapClient.ACTION_MESSAGE_DELETED_STATUS_CHANGED}; + MceSetMessageStatusReceiver receiver = new MceSetMessageStatusReceiver(expectedFlags); + addReceiver(receiver, actions); + return receiver; + } + private void removeReceiver(BroadcastReceiver receiver) { mContext.unregisterReceiver(receiver); mReceivers.remove(receiver); @@ -1456,6 +1599,10 @@ public class BluetoothTestUtils extends Assert { if (mPan != null) { return mPan; } + case BluetoothProfile.MAP_CLIENT: + if (mMce != null) { + return mMce; + } break; default: return null; @@ -1483,6 +1630,11 @@ public class BluetoothTestUtils extends Assert { sleep(POLL_TIME); } return mPan; + case BluetoothProfile.MAP_CLIENT: + while (mMce == null && System.currentTimeMillis() - s < CONNECT_PROXY_TIMEOUT) { + sleep(POLL_TIME); + } + return mMce; default: return null; } diff --git a/core/tests/coretests/src/android/service/euicc/EuiccProfileInfoTest.java b/core/tests/coretests/src/android/service/euicc/EuiccProfileInfoTest.java index d00d052db590..0af8c728aba3 100644 --- a/core/tests/coretests/src/android/service/euicc/EuiccProfileInfoTest.java +++ b/core/tests/coretests/src/android/service/euicc/EuiccProfileInfoTest.java @@ -154,6 +154,29 @@ public class EuiccProfileInfoTest { } @Test + public void testBuilder_BasedOnAnotherProfileWithEmptyAccessRules() { + EuiccProfileInfo p = + new EuiccProfileInfo.Builder("21430000000000006587") + .setNickname("profile nickname") + .setProfileName("profile name") + .setServiceProviderName("service provider") + .setCarrierIdentifier( + new CarrierIdentifier( + new byte[] {0x23, 0x45, 0x67}, + "123", + "45")) + .setState(EuiccProfileInfo.PROFILE_STATE_ENABLED) + .setProfileClass(EuiccProfileInfo.PROFILE_CLASS_OPERATIONAL) + .setPolicyRules(EuiccProfileInfo.POLICY_RULE_DO_NOT_DELETE) + .setUiccAccessRule(null) + .build(); + + EuiccProfileInfo copied = new EuiccProfileInfo.Builder(p).build(); + + assertEquals(null, copied.getUiccAccessRules()); + } + + @Test public void testEqualsHashCode() { EuiccProfileInfo p = new EuiccProfileInfo.Builder("21430000000000006587") diff --git a/core/tests/coretests/src/android/text/format/DateFormatTest.java b/core/tests/coretests/src/android/text/format/DateFormatTest.java index 5a0a84db5905..fa1d56f2e68d 100644 --- a/core/tests/coretests/src/android/text/format/DateFormatTest.java +++ b/core/tests/coretests/src/android/text/format/DateFormatTest.java @@ -16,8 +16,10 @@ package android.text.format; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import android.platform.test.annotations.Presubmit; @@ -27,6 +29,7 @@ import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.Arrays; import java.util.Locale; @Presubmit @@ -55,4 +58,71 @@ public class DateFormatTest { assertFalse(DateFormat.is24HourLocale(Locale.US)); assertTrue(DateFormat.is24HourLocale(Locale.GERMANY)); } + + @Test + public void testGetDateFormatOrder() { + // lv and fa use differing orders depending on whether you're using numeric or + // textual months. + Locale lv = new Locale("lv"); + assertEquals("[d, M, y]", Arrays.toString(DateFormat.getDateFormatOrder( + best(lv, "yyyy-M-dd")))); + assertEquals("[y, d, M]", Arrays.toString(DateFormat.getDateFormatOrder( + best(lv, "yyyy-MMM-dd")))); + assertEquals("[d, M, \u0000]", Arrays.toString(DateFormat.getDateFormatOrder( + best(lv, "MMM-dd")))); + Locale fa = new Locale("fa"); + assertEquals("[y, M, d]", Arrays.toString(DateFormat.getDateFormatOrder( + best(fa, "yyyy-M-dd")))); + assertEquals("[d, M, y]", Arrays.toString(DateFormat.getDateFormatOrder( + best(fa, "yyyy-MMM-dd")))); + assertEquals("[d, M, \u0000]", Arrays.toString(DateFormat.getDateFormatOrder( + best(fa, "MMM-dd")))); + + // English differs on each side of the Atlantic. + Locale enUS = Locale.US; + assertEquals("[M, d, y]", Arrays.toString(DateFormat.getDateFormatOrder( + best(enUS, "yyyy-M-dd")))); + assertEquals("[M, d, y]", Arrays.toString(DateFormat.getDateFormatOrder( + best(enUS, "yyyy-MMM-dd")))); + assertEquals("[M, d, \u0000]", Arrays.toString(DateFormat.getDateFormatOrder( + best(enUS, "MMM-dd")))); + Locale enGB = Locale.UK; + assertEquals("[d, M, y]", Arrays.toString(DateFormat.getDateFormatOrder( + best(enGB, "yyyy-M-dd")))); + assertEquals("[d, M, y]", Arrays.toString(DateFormat.getDateFormatOrder( + best(enGB, "yyyy-MMM-dd")))); + assertEquals("[d, M, \u0000]", Arrays.toString(DateFormat.getDateFormatOrder( + best(enGB, "MMM-dd")))); + + assertEquals("[y, M, d]", Arrays.toString(DateFormat.getDateFormatOrder( + "yyyy - 'why' '' 'ddd' MMM-dd"))); + + try { + DateFormat.getDateFormatOrder("the quick brown fox jumped over the lazy dog"); + fail(); + } catch (IllegalArgumentException expected) { + } + + try { + DateFormat.getDateFormatOrder("'"); + fail(); + } catch (IllegalArgumentException expected) { + } + + try { + DateFormat.getDateFormatOrder("yyyy'"); + fail(); + } catch (IllegalArgumentException expected) { + } + + try { + DateFormat.getDateFormatOrder("yyyy'MMM"); + fail(); + } catch (IllegalArgumentException expected) { + } + } + + private static String best(Locale l, String skeleton) { + return DateFormat.getBestDateTimePattern(l, skeleton); + } } diff --git a/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java new file mode 100644 index 000000000000..d9ba8fb81d3c --- /dev/null +++ b/core/tests/coretests/src/android/text/format/RelativeDateTimeFormatterTest.java @@ -0,0 +1,762 @@ +/* + * Copyright (C) 2015 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.text.format; + +import static android.text.format.DateUtilsBridge.FORMAT_ABBREV_ALL; +import static android.text.format.DateUtilsBridge.FORMAT_ABBREV_RELATIVE; +import static android.text.format.DateUtilsBridge.FORMAT_NO_YEAR; +import static android.text.format.DateUtilsBridge.FORMAT_NUMERIC_DATE; +import static android.text.format.DateUtilsBridge.FORMAT_SHOW_YEAR; +import static android.text.format.RelativeDateTimeFormatter.DAY_IN_MILLIS; +import static android.text.format.RelativeDateTimeFormatter.HOUR_IN_MILLIS; +import static android.text.format.RelativeDateTimeFormatter.MINUTE_IN_MILLIS; +import static android.text.format.RelativeDateTimeFormatter.SECOND_IN_MILLIS; +import static android.text.format.RelativeDateTimeFormatter.WEEK_IN_MILLIS; +import static android.text.format.RelativeDateTimeFormatter.YEAR_IN_MILLIS; +import static android.text.format.RelativeDateTimeFormatter.getRelativeDateTimeString; +import static android.text.format.RelativeDateTimeFormatter.getRelativeTimeSpanString; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; + +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Calendar; +import java.util.Locale; +import java.util.TimeZone; + +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class RelativeDateTimeFormatterTest { + + // Tests adopted from CTS tests for DateUtils.getRelativeTimeSpanString. + @Test + public void test_getRelativeTimeSpanStringCTS() throws Exception { + Locale en_US = new Locale("en", "US"); + TimeZone tz = TimeZone.getTimeZone("GMT"); + Calendar cal = Calendar.getInstance(tz, en_US); + // Feb 5, 2015 at 10:50 GMT + cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0); + final long baseTime = cal.getTimeInMillis(); + + assertEquals("0 minutes ago", + getRelativeTimeSpanString(en_US, tz, baseTime - SECOND_IN_MILLIS, baseTime, + MINUTE_IN_MILLIS, 0)); + assertEquals("In 0 minutes", + getRelativeTimeSpanString(en_US, tz, baseTime + SECOND_IN_MILLIS, baseTime, + MINUTE_IN_MILLIS, 0)); + + assertEquals("1 minute ago", + getRelativeTimeSpanString(en_US, tz, 0, MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 0)); + assertEquals("In 1 minute", + getRelativeTimeSpanString(en_US, tz, MINUTE_IN_MILLIS, 0, MINUTE_IN_MILLIS, 0)); + + assertEquals("42 minutes ago", + getRelativeTimeSpanString(en_US, tz, baseTime - 42 * MINUTE_IN_MILLIS, baseTime, + MINUTE_IN_MILLIS, 0)); + assertEquals("In 42 minutes", + getRelativeTimeSpanString(en_US, tz, baseTime + 42 * MINUTE_IN_MILLIS, baseTime, + MINUTE_IN_MILLIS, 0)); + + final long TWO_HOURS_IN_MS = 2 * HOUR_IN_MILLIS; + assertEquals("2 hours ago", + getRelativeTimeSpanString(en_US, tz, baseTime - TWO_HOURS_IN_MS, baseTime, + MINUTE_IN_MILLIS, FORMAT_NUMERIC_DATE)); + assertEquals("In 2 hours", + getRelativeTimeSpanString(en_US, tz, baseTime + TWO_HOURS_IN_MS, baseTime, + MINUTE_IN_MILLIS, FORMAT_NUMERIC_DATE)); + + assertEquals("In 42 min.", + getRelativeTimeSpanString(en_US, tz, baseTime + (42 * MINUTE_IN_MILLIS), baseTime, + MINUTE_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + + assertEquals("Tomorrow", + getRelativeTimeSpanString(en_US, tz, DAY_IN_MILLIS, 0, DAY_IN_MILLIS, 0)); + assertEquals("In 2 days", + getRelativeTimeSpanString(en_US, tz, 2 * DAY_IN_MILLIS, 0, DAY_IN_MILLIS, 0)); + assertEquals("Yesterday", + getRelativeTimeSpanString(en_US, tz, 0, DAY_IN_MILLIS, DAY_IN_MILLIS, 0)); + assertEquals("2 days ago", + getRelativeTimeSpanString(en_US, tz, 0, 2 * DAY_IN_MILLIS, DAY_IN_MILLIS, 0)); + + final long DAY_DURATION = 5 * 24 * 60 * 60 * 1000; + assertEquals("5 days ago", + getRelativeTimeSpanString(en_US, tz, baseTime - DAY_DURATION, baseTime, + DAY_IN_MILLIS, 0)); + } + + private void test_getRelativeTimeSpanString_helper(long delta, long minResolution, int flags, + String expectedInPast, + String expectedInFuture) throws Exception { + Locale en_US = new Locale("en", "US"); + TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); + Calendar cal = Calendar.getInstance(tz, en_US); + // Feb 5, 2015 at 10:50 PST + cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0); + final long base = cal.getTimeInMillis(); + + assertEquals(expectedInPast, + getRelativeTimeSpanString(en_US, tz, base - delta, base, minResolution, flags)); + assertEquals(expectedInFuture, + getRelativeTimeSpanString(en_US, tz, base + delta, base, minResolution, flags)); + } + + private void test_getRelativeTimeSpanString_helper(long delta, long minResolution, + String expectedInPast, + String expectedInFuture) throws Exception { + test_getRelativeTimeSpanString_helper(delta, minResolution, 0, expectedInPast, + expectedInFuture); + } + + @Test + public void test_getRelativeTimeSpanString() throws Exception { + + test_getRelativeTimeSpanString_helper(0 * SECOND_IN_MILLIS, 0, "0 seconds ago", + "0 seconds ago"); + test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, 0, "1 minute ago", + "In 1 minute"); + test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, 0, "1 minute ago", + "In 1 minute"); + test_getRelativeTimeSpanString_helper(5 * DAY_IN_MILLIS, 0, "5 days ago", "In 5 days"); + + test_getRelativeTimeSpanString_helper(0 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, + "0 seconds ago", + "0 seconds ago"); + test_getRelativeTimeSpanString_helper(1 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, + "1 second ago", + "In 1 second"); + test_getRelativeTimeSpanString_helper(2 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, + "2 seconds ago", + "In 2 seconds"); + test_getRelativeTimeSpanString_helper(25 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, + "25 seconds ago", + "In 25 seconds"); + test_getRelativeTimeSpanString_helper(75 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, + "1 minute ago", + "In 1 minute"); + test_getRelativeTimeSpanString_helper(5000 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, + "1 hour ago", + "In 1 hour"); + + test_getRelativeTimeSpanString_helper(0 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, + "0 minutes ago", + "0 minutes ago"); + test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, + "1 minute ago", + "In 1 minute"); + test_getRelativeTimeSpanString_helper(2 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, + "2 minutes ago", + "In 2 minutes"); + test_getRelativeTimeSpanString_helper(25 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, + "25 minutes ago", + "In 25 minutes"); + test_getRelativeTimeSpanString_helper(75 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, "1 hour ago", + "In 1 hour"); + test_getRelativeTimeSpanString_helper(720 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, + "12 hours ago", + "In 12 hours"); + + test_getRelativeTimeSpanString_helper(0 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "0 hours ago", + "0 hours ago"); + test_getRelativeTimeSpanString_helper(1 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "1 hour ago", + "In 1 hour"); + test_getRelativeTimeSpanString_helper(2 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "2 hours ago", + "In 2 hours"); + test_getRelativeTimeSpanString_helper(5 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "5 hours ago", + "In 5 hours"); + test_getRelativeTimeSpanString_helper(20 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, "20 hours ago", + "In 20 hours"); + + test_getRelativeTimeSpanString_helper(0 * DAY_IN_MILLIS, DAY_IN_MILLIS, "Today", "Today"); + test_getRelativeTimeSpanString_helper(20 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Yesterday", + "Tomorrow"); + test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Yesterday", + "Tomorrow"); + test_getRelativeTimeSpanString_helper(2 * DAY_IN_MILLIS, DAY_IN_MILLIS, "2 days ago", + "In 2 days"); + test_getRelativeTimeSpanString_helper(25 * DAY_IN_MILLIS, DAY_IN_MILLIS, "January 11", + "March 2"); + + test_getRelativeTimeSpanString_helper(0 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "0 weeks ago", + "0 weeks ago"); + test_getRelativeTimeSpanString_helper(1 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "1 week ago", + "In 1 week"); + test_getRelativeTimeSpanString_helper(2 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "2 weeks ago", + "In 2 weeks"); + test_getRelativeTimeSpanString_helper(25 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, "25 weeks ago", + "In 25 weeks"); + + // duration >= minResolution + test_getRelativeTimeSpanString_helper(30 * SECOND_IN_MILLIS, 0, "30 seconds ago", + "In 30 seconds"); + test_getRelativeTimeSpanString_helper(30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, + "30 minutes ago", "In 30 minutes"); + test_getRelativeTimeSpanString_helper(30 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, "Yesterday", + "Tomorrow"); + test_getRelativeTimeSpanString_helper(5 * DAY_IN_MILLIS, MINUTE_IN_MILLIS, "5 days ago", + "In 5 days"); + test_getRelativeTimeSpanString_helper(30 * WEEK_IN_MILLIS, MINUTE_IN_MILLIS, + "July 10, 2014", + "September 3"); + test_getRelativeTimeSpanString_helper(5 * 365 * DAY_IN_MILLIS, MINUTE_IN_MILLIS, + "February 6, 2010", "February 4, 2020"); + + test_getRelativeTimeSpanString_helper(60 * SECOND_IN_MILLIS, MINUTE_IN_MILLIS, + "1 minute ago", + "In 1 minute"); + test_getRelativeTimeSpanString_helper(120 * SECOND_IN_MILLIS - 1, MINUTE_IN_MILLIS, + "1 minute ago", "In 1 minute"); + test_getRelativeTimeSpanString_helper(60 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, "1 hour ago", + "In 1 hour"); + test_getRelativeTimeSpanString_helper(120 * MINUTE_IN_MILLIS - 1, HOUR_IN_MILLIS, + "1 hour ago", + "In 1 hour"); + test_getRelativeTimeSpanString_helper(2 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Today", "Today"); + test_getRelativeTimeSpanString_helper(12 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Yesterday", + "Today"); + test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "Yesterday", + "Tomorrow"); + test_getRelativeTimeSpanString_helper(48 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "2 days ago", + "In 2 days"); + test_getRelativeTimeSpanString_helper(45 * HOUR_IN_MILLIS, DAY_IN_MILLIS, "2 days ago", + "In 2 days"); + test_getRelativeTimeSpanString_helper(7 * DAY_IN_MILLIS, WEEK_IN_MILLIS, "1 week ago", + "In 1 week"); + test_getRelativeTimeSpanString_helper(14 * DAY_IN_MILLIS - 1, WEEK_IN_MILLIS, "1 week ago", + "In 1 week"); + + // duration < minResolution + test_getRelativeTimeSpanString_helper(59 * SECOND_IN_MILLIS, MINUTE_IN_MILLIS, + "0 minutes ago", + "In 0 minutes"); + test_getRelativeTimeSpanString_helper(59 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, "0 hours ago", + "In 0 hours"); + test_getRelativeTimeSpanString_helper(HOUR_IN_MILLIS - 1, HOUR_IN_MILLIS, "0 hours ago", + "In 0 hours"); + test_getRelativeTimeSpanString_helper(DAY_IN_MILLIS - 1, DAY_IN_MILLIS, "Yesterday", + "Tomorrow"); + test_getRelativeTimeSpanString_helper(20 * SECOND_IN_MILLIS, WEEK_IN_MILLIS, "0 weeks ago", + "In 0 weeks"); + test_getRelativeTimeSpanString_helper(WEEK_IN_MILLIS - 1, WEEK_IN_MILLIS, "0 weeks ago", + "In 0 weeks"); + } + + @Test + public void test_getRelativeTimeSpanStringAbbrev() throws Exception { + int flags = FORMAT_ABBREV_RELATIVE; + + test_getRelativeTimeSpanString_helper(0 * SECOND_IN_MILLIS, 0, flags, "0 sec. ago", + "0 sec. ago"); + test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, 0, flags, "1 min. ago", + "In 1 min."); + test_getRelativeTimeSpanString_helper(5 * DAY_IN_MILLIS, 0, flags, "5 days ago", + "In 5 days"); + + test_getRelativeTimeSpanString_helper(0 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags, + "0 sec. ago", "0 sec. ago"); + test_getRelativeTimeSpanString_helper(1 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags, + "1 sec. ago", "In 1 sec."); + test_getRelativeTimeSpanString_helper(2 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags, + "2 sec. ago", "In 2 sec."); + test_getRelativeTimeSpanString_helper(25 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags, + "25 sec. ago", "In 25 sec."); + test_getRelativeTimeSpanString_helper(75 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags, + "1 min. ago", "In 1 min."); + test_getRelativeTimeSpanString_helper(5000 * SECOND_IN_MILLIS, SECOND_IN_MILLIS, flags, + "1 hr. ago", "In 1 hr."); + + test_getRelativeTimeSpanString_helper(0 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "0 min. ago", "0 min. ago"); + test_getRelativeTimeSpanString_helper(1 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "1 min. ago", "In 1 min."); + test_getRelativeTimeSpanString_helper(2 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "2 min. ago", "In 2 min."); + test_getRelativeTimeSpanString_helper(25 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "25 min. ago", "In 25 min."); + test_getRelativeTimeSpanString_helper(75 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "1 hr. ago", "In 1 hr."); + test_getRelativeTimeSpanString_helper(720 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "12 hr. ago", "In 12 hr."); + + test_getRelativeTimeSpanString_helper(0 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags, + "0 hr. ago", "0 hr. ago"); + test_getRelativeTimeSpanString_helper(1 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags, + "1 hr. ago", "In 1 hr."); + test_getRelativeTimeSpanString_helper(2 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags, + "2 hr. ago", "In 2 hr."); + test_getRelativeTimeSpanString_helper(5 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags, + "5 hr. ago", "In 5 hr."); + test_getRelativeTimeSpanString_helper(20 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, flags, + "20 hr. ago", "In 20 hr."); + + test_getRelativeTimeSpanString_helper(0 * DAY_IN_MILLIS, DAY_IN_MILLIS, flags, "Today", + "Today"); + test_getRelativeTimeSpanString_helper(20 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, + "Yesterday", "Tomorrow"); + test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, + "Yesterday", "Tomorrow"); + test_getRelativeTimeSpanString_helper(2 * DAY_IN_MILLIS, DAY_IN_MILLIS, flags, + "2 days ago", "In 2 days"); + test_getRelativeTimeSpanString_helper(25 * DAY_IN_MILLIS, DAY_IN_MILLIS, flags, + "January 11", "March 2"); + + test_getRelativeTimeSpanString_helper(0 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags, + "0 wk. ago", "0 wk. ago"); + test_getRelativeTimeSpanString_helper(1 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags, + "1 wk. ago", "In 1 wk."); + test_getRelativeTimeSpanString_helper(2 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags, + "2 wk. ago", "In 2 wk."); + test_getRelativeTimeSpanString_helper(25 * WEEK_IN_MILLIS, WEEK_IN_MILLIS, flags, + "25 wk. ago", "In 25 wk."); + + // duration >= minResolution + test_getRelativeTimeSpanString_helper(30 * SECOND_IN_MILLIS, 0, flags, "30 sec. ago", + "In 30 sec."); + test_getRelativeTimeSpanString_helper(30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "30 min. ago", "In 30 min."); + test_getRelativeTimeSpanString_helper(30 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "Yesterday", "Tomorrow"); + test_getRelativeTimeSpanString_helper(5 * DAY_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "5 days ago", "In 5 days"); + test_getRelativeTimeSpanString_helper(30 * WEEK_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "July 10, 2014", "September 3"); + test_getRelativeTimeSpanString_helper(5 * 365 * DAY_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "February 6, 2010", "February 4, 2020"); + + test_getRelativeTimeSpanString_helper(60 * SECOND_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "1 min. ago", "In 1 min."); + test_getRelativeTimeSpanString_helper(120 * SECOND_IN_MILLIS - 1, MINUTE_IN_MILLIS, flags, + "1 min. ago", "In 1 min."); + test_getRelativeTimeSpanString_helper(60 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, flags, + "1 hr. ago", "In 1 hr."); + test_getRelativeTimeSpanString_helper(120 * MINUTE_IN_MILLIS - 1, HOUR_IN_MILLIS, flags, + "1 hr. ago", "In 1 hr."); + test_getRelativeTimeSpanString_helper(2 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, "Today", + "Today"); + test_getRelativeTimeSpanString_helper(12 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, + "Yesterday", "Today"); + test_getRelativeTimeSpanString_helper(24 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, + "Yesterday", "Tomorrow"); + test_getRelativeTimeSpanString_helper(48 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, + "2 days ago", "In 2 days"); + test_getRelativeTimeSpanString_helper(45 * HOUR_IN_MILLIS, DAY_IN_MILLIS, flags, + "2 days ago", "In 2 days"); + test_getRelativeTimeSpanString_helper(7 * DAY_IN_MILLIS, WEEK_IN_MILLIS, flags, + "1 wk. ago", "In 1 wk."); + test_getRelativeTimeSpanString_helper(14 * DAY_IN_MILLIS - 1, WEEK_IN_MILLIS, flags, + "1 wk. ago", "In 1 wk."); + + // duration < minResolution + test_getRelativeTimeSpanString_helper(59 * SECOND_IN_MILLIS, MINUTE_IN_MILLIS, flags, + "0 min. ago", "In 0 min."); + test_getRelativeTimeSpanString_helper(59 * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, flags, + "0 hr. ago", "In 0 hr."); + test_getRelativeTimeSpanString_helper(HOUR_IN_MILLIS - 1, HOUR_IN_MILLIS, flags, + "0 hr. ago", "In 0 hr."); + test_getRelativeTimeSpanString_helper(DAY_IN_MILLIS - 1, DAY_IN_MILLIS, flags, + "Yesterday", "Tomorrow"); + test_getRelativeTimeSpanString_helper(20 * SECOND_IN_MILLIS, WEEK_IN_MILLIS, flags, + "0 wk. ago", "In 0 wk."); + test_getRelativeTimeSpanString_helper(WEEK_IN_MILLIS - 1, WEEK_IN_MILLIS, flags, + "0 wk. ago", "In 0 wk."); + + } + + @Test + public void test_getRelativeTimeSpanStringGerman() throws Exception { + // Bug: 19744876 + // We need to specify the timezone and the time explicitly. Otherwise it + // may not always give a correct answer of "tomorrow" by using + // (now + DAY_IN_MILLIS). + Locale de_DE = new Locale("de", "DE"); + TimeZone tz = TimeZone.getTimeZone("Europe/Berlin"); + Calendar cal = Calendar.getInstance(tz, de_DE); + // Feb 5, 2015 at 10:50 CET + cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0); + final long now = cal.getTimeInMillis(); + + // 42 minutes ago + assertEquals("Vor 42 Minuten", getRelativeTimeSpanString(de_DE, tz, + now - 42 * MINUTE_IN_MILLIS, now, MINUTE_IN_MILLIS, 0)); + // In 42 minutes + assertEquals("In 42 Minuten", getRelativeTimeSpanString(de_DE, tz, + now + 42 * MINUTE_IN_MILLIS, now, MINUTE_IN_MILLIS, 0)); + // Yesterday + assertEquals("Gestern", getRelativeTimeSpanString(de_DE, tz, + now - DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + // The day before yesterday + assertEquals("Vorgestern", getRelativeTimeSpanString(de_DE, tz, + now - 2 * DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + // Tomorrow + assertEquals("Morgen", getRelativeTimeSpanString(de_DE, tz, + now + DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + // The day after tomorrow + assertEquals("Übermorgen", getRelativeTimeSpanString(de_DE, tz, + now + 2 * DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + } + + @Test + public void test_getRelativeTimeSpanStringFrench() throws Exception { + Locale fr_FR = new Locale("fr", "FR"); + TimeZone tz = TimeZone.getTimeZone("Europe/Paris"); + Calendar cal = Calendar.getInstance(tz, fr_FR); + // Feb 5, 2015 at 10:50 CET + cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0); + final long now = cal.getTimeInMillis(); + + // 42 minutes ago + assertEquals("Il y a 42 minutes", getRelativeTimeSpanString(fr_FR, tz, + now - (42 * MINUTE_IN_MILLIS), now, MINUTE_IN_MILLIS, 0)); + // In 42 minutes + assertEquals("Dans 42 minutes", getRelativeTimeSpanString(fr_FR, tz, + now + (42 * MINUTE_IN_MILLIS), now, MINUTE_IN_MILLIS, 0)); + // Yesterday + assertEquals("Hier", getRelativeTimeSpanString(fr_FR, tz, + now - DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + // The day before yesterday + assertEquals("Avant-hier", getRelativeTimeSpanString(fr_FR, tz, + now - 2 * DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + // Tomorrow + assertEquals("Demain", getRelativeTimeSpanString(fr_FR, tz, + now + DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + // The day after tomorrow + assertEquals("Après-demain", getRelativeTimeSpanString(fr_FR, tz, + now + 2 * DAY_IN_MILLIS, now, DAY_IN_MILLIS, 0)); + } + + // Tests adopted from CTS tests for DateUtils.getRelativeDateTimeString. + @Test + public void test_getRelativeDateTimeStringCTS() throws Exception { + Locale en_US = Locale.getDefault(); + TimeZone tz = TimeZone.getDefault(); + final long baseTime = System.currentTimeMillis(); + + final long DAY_DURATION = 5 * 24 * 60 * 60 * 1000; + assertNotNull(getRelativeDateTimeString(en_US, tz, baseTime - DAY_DURATION, baseTime, + MINUTE_IN_MILLIS, DAY_IN_MILLIS, + FORMAT_NUMERIC_DATE)); + } + + @Test + public void test_getRelativeDateTimeString() throws Exception { + Locale en_US = new Locale("en", "US"); + TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); + Calendar cal = Calendar.getInstance(tz, en_US); + // Feb 5, 2015 at 10:50 PST + cal.set(2015, Calendar.FEBRUARY, 5, 10, 50, 0); + final long base = cal.getTimeInMillis(); + + assertEquals("5 seconds ago, 10:49 AM", + getRelativeDateTimeString(en_US, tz, base - 5 * SECOND_IN_MILLIS, base, 0, + MINUTE_IN_MILLIS, 0)); + assertEquals("5 min. ago, 10:45 AM", + getRelativeDateTimeString(en_US, tz, base - 5 * MINUTE_IN_MILLIS, base, 0, + HOUR_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + assertEquals("0 hr. ago, 10:45 AM", + getRelativeDateTimeString(en_US, tz, base - 5 * MINUTE_IN_MILLIS, base, + HOUR_IN_MILLIS, DAY_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + assertEquals("5 hours ago, 5:50 AM", + getRelativeDateTimeString(en_US, tz, base - 5 * HOUR_IN_MILLIS, base, + HOUR_IN_MILLIS, DAY_IN_MILLIS, 0)); + assertEquals("Yesterday, 7:50 PM", + getRelativeDateTimeString(en_US, tz, base - 15 * HOUR_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + assertEquals("5 days ago, 10:50 AM", + getRelativeDateTimeString(en_US, tz, base - 5 * DAY_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + assertEquals("Jan 29, 10:50 AM", + getRelativeDateTimeString(en_US, tz, base - 7 * DAY_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + assertEquals("11/27/2014, 10:50 AM", + getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + assertEquals("11/27/2014, 10:50 AM", + getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0, + YEAR_IN_MILLIS, 0)); + + // User-supplied flags should be ignored when formatting the date clause. + final int FORMAT_SHOW_WEEKDAY = 0x00002; + assertEquals("11/27/2014, 10:50 AM", + getRelativeDateTimeString(en_US, tz, base - 10 * WEEK_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, + FORMAT_ABBREV_ALL | FORMAT_SHOW_WEEKDAY)); + } + + @Test + public void test_getRelativeDateTimeStringDST() throws Exception { + Locale en_US = new Locale("en", "US"); + TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); + Calendar cal = Calendar.getInstance(tz, en_US); + + // DST starts on Mar 9, 2014 at 2:00 AM. + // So 5 hours before 3:15 AM should be formatted as 'Yesterday, 9:15 PM'. + cal.set(2014, Calendar.MARCH, 9, 3, 15, 0); + long base = cal.getTimeInMillis(); + assertEquals("Yesterday, 9:15 PM", + getRelativeDateTimeString(en_US, tz, base - 5 * HOUR_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + + // 1 hour after 2:00 AM should be formatted as 'In 1 hour, 4:00 AM'. + cal.set(2014, Calendar.MARCH, 9, 2, 0, 0); + base = cal.getTimeInMillis(); + assertEquals("In 1 hour, 4:00 AM", + getRelativeDateTimeString(en_US, tz, base + 1 * HOUR_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + + // DST ends on Nov 2, 2014 at 2:00 AM. Clocks are turned backward 1 hour to + // 1:00 AM. 8 hours before 5:20 AM should be 'Yesterday, 10:20 PM'. + cal.set(2014, Calendar.NOVEMBER, 2, 5, 20, 0); + base = cal.getTimeInMillis(); + assertEquals("Yesterday, 10:20 PM", + getRelativeDateTimeString(en_US, tz, base - 8 * HOUR_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + + cal.set(2014, Calendar.NOVEMBER, 2, 0, 45, 0); + base = cal.getTimeInMillis(); + // 45 minutes after 0:45 AM should be 'In 45 minutes, 1:30 AM'. + assertEquals("In 45 minutes, 1:30 AM", + getRelativeDateTimeString(en_US, tz, base + 45 * MINUTE_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + // 45 minutes later, it should be 'In 45 minutes, 1:15 AM'. + assertEquals("In 45 minutes, 1:15 AM", + getRelativeDateTimeString(en_US, tz, base + 90 * MINUTE_IN_MILLIS, + base + 45 * MINUTE_IN_MILLIS, 0, WEEK_IN_MILLIS, 0)); + // Another 45 minutes later, it should be 'In 45 minutes, 2:00 AM'. + assertEquals("In 45 minutes, 2:00 AM", + getRelativeDateTimeString(en_US, tz, base + 135 * MINUTE_IN_MILLIS, + base + 90 * MINUTE_IN_MILLIS, 0, WEEK_IN_MILLIS, 0)); + } + + @Test + public void test_getRelativeDateTimeStringItalian() throws Exception { + Locale it_IT = new Locale("it", "IT"); + TimeZone tz = TimeZone.getTimeZone("Europe/Rome"); + Calendar cal = Calendar.getInstance(tz, it_IT); + // 05 febbraio 2015 20:15 + cal.set(2015, Calendar.FEBRUARY, 5, 20, 15, 0); + final long base = cal.getTimeInMillis(); + + assertEquals("5 secondi fa, 20:14", + getRelativeDateTimeString(it_IT, tz, base - 5 * SECOND_IN_MILLIS, base, 0, + MINUTE_IN_MILLIS, 0)); + assertEquals("5 min fa, 20:10", + getRelativeDateTimeString(it_IT, tz, base - 5 * MINUTE_IN_MILLIS, base, 0, + HOUR_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + assertEquals("0 h fa, 20:10", + getRelativeDateTimeString(it_IT, tz, base - 5 * MINUTE_IN_MILLIS, base, + HOUR_IN_MILLIS, DAY_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + assertEquals("Ieri, 22:15", + getRelativeDateTimeString(it_IT, tz, base - 22 * HOUR_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + assertEquals("5 giorni fa, 20:15", + getRelativeDateTimeString(it_IT, tz, base - 5 * DAY_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + assertEquals("27/11/2014, 20:15", + getRelativeDateTimeString(it_IT, tz, base - 10 * WEEK_IN_MILLIS, base, 0, + WEEK_IN_MILLIS, 0)); + } + + // http://b/5252772: detect the actual date difference + @Test + public void test5252772() throws Exception { + Locale en_US = new Locale("en", "US"); + TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); + + // Now is Sep 2, 2011, 10:23 AM PDT. + Calendar nowCalendar = Calendar.getInstance(tz, en_US); + nowCalendar.set(2011, Calendar.SEPTEMBER, 2, 10, 23, 0); + final long now = nowCalendar.getTimeInMillis(); + + // Sep 1, 2011, 10:24 AM + Calendar yesterdayCalendar1 = Calendar.getInstance(tz, en_US); + yesterdayCalendar1.set(2011, Calendar.SEPTEMBER, 1, 10, 24, 0); + long yesterday1 = yesterdayCalendar1.getTimeInMillis(); + assertEquals("Yesterday, 10:24 AM", + getRelativeDateTimeString(en_US, tz, yesterday1, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + + // Sep 1, 2011, 10:22 AM + Calendar yesterdayCalendar2 = Calendar.getInstance(tz, en_US); + yesterdayCalendar2.set(2011, Calendar.SEPTEMBER, 1, 10, 22, 0); + long yesterday2 = yesterdayCalendar2.getTimeInMillis(); + assertEquals("Yesterday, 10:22 AM", + getRelativeDateTimeString(en_US, tz, yesterday2, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + + // Aug 31, 2011, 10:24 AM + Calendar twoDaysAgoCalendar1 = Calendar.getInstance(tz, en_US); + twoDaysAgoCalendar1.set(2011, Calendar.AUGUST, 31, 10, 24, 0); + long twoDaysAgo1 = twoDaysAgoCalendar1.getTimeInMillis(); + assertEquals("2 days ago, 10:24 AM", + getRelativeDateTimeString(en_US, tz, twoDaysAgo1, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + + // Aug 31, 2011, 10:22 AM + Calendar twoDaysAgoCalendar2 = Calendar.getInstance(tz, en_US); + twoDaysAgoCalendar2.set(2011, Calendar.AUGUST, 31, 10, 22, 0); + long twoDaysAgo2 = twoDaysAgoCalendar2.getTimeInMillis(); + assertEquals("2 days ago, 10:22 AM", + getRelativeDateTimeString(en_US, tz, twoDaysAgo2, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + + // Sep 3, 2011, 10:22 AM + Calendar tomorrowCalendar1 = Calendar.getInstance(tz, en_US); + tomorrowCalendar1.set(2011, Calendar.SEPTEMBER, 3, 10, 22, 0); + long tomorrow1 = tomorrowCalendar1.getTimeInMillis(); + assertEquals("Tomorrow, 10:22 AM", + getRelativeDateTimeString(en_US, tz, tomorrow1, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + + // Sep 3, 2011, 10:24 AM + Calendar tomorrowCalendar2 = Calendar.getInstance(tz, en_US); + tomorrowCalendar2.set(2011, Calendar.SEPTEMBER, 3, 10, 24, 0); + long tomorrow2 = tomorrowCalendar2.getTimeInMillis(); + assertEquals("Tomorrow, 10:24 AM", + getRelativeDateTimeString(en_US, tz, tomorrow2, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + + // Sep 4, 2011, 10:22 AM + Calendar twoDaysLaterCalendar1 = Calendar.getInstance(tz, en_US); + twoDaysLaterCalendar1.set(2011, Calendar.SEPTEMBER, 4, 10, 22, 0); + long twoDaysLater1 = twoDaysLaterCalendar1.getTimeInMillis(); + assertEquals("In 2 days, 10:22 AM", + getRelativeDateTimeString(en_US, tz, twoDaysLater1, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + + // Sep 4, 2011, 10:24 AM + Calendar twoDaysLaterCalendar2 = Calendar.getInstance(tz, en_US); + twoDaysLaterCalendar2.set(2011, Calendar.SEPTEMBER, 4, 10, 24, 0); + long twoDaysLater2 = twoDaysLaterCalendar2.getTimeInMillis(); + assertEquals("In 2 days, 10:24 AM", + getRelativeDateTimeString(en_US, tz, twoDaysLater2, now, MINUTE_IN_MILLIS, + WEEK_IN_MILLIS, 0)); + } + + // b/19822016: show / hide the year based on the dates in the arguments. + @Test + public void test_bug19822016() throws Exception { + Locale en_US = new Locale("en", "US"); + TimeZone tz = TimeZone.getTimeZone("America/Los_Angeles"); + Calendar cal = Calendar.getInstance(tz, en_US); + // Feb 5, 2012 at 10:50 PST + cal.set(2012, Calendar.FEBRUARY, 5, 10, 50, 0); + long base = cal.getTimeInMillis(); + + assertEquals("Feb 5, 5:50 AM", getRelativeDateTimeString(en_US, tz, + base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_IN_MILLIS, 0)); + assertEquals("Jan 29, 10:50 AM", getRelativeDateTimeString(en_US, tz, + base - 7 * DAY_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0)); + assertEquals("11/27/2011, 10:50 AM", getRelativeDateTimeString(en_US, tz, + base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0)); + + assertEquals("January 6", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); + assertEquals("January 6", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); + assertEquals("January 6, 2012", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); + assertEquals("December 7, 2011", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); + assertEquals("December 7, 2011", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); + assertEquals("December 7", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); + + // Feb 5, 2018 at 10:50 PST + cal.set(2018, Calendar.FEBRUARY, 5, 10, 50, 0); + base = cal.getTimeInMillis(); + assertEquals("Feb 5, 5:50 AM", getRelativeDateTimeString(en_US, tz, + base - 5 * HOUR_IN_MILLIS, base, 0, MINUTE_IN_MILLIS, 0)); + assertEquals("Jan 29, 10:50 AM", getRelativeDateTimeString(en_US, tz, + base - 7 * DAY_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0)); + assertEquals("11/27/2017, 10:50 AM", getRelativeDateTimeString(en_US, tz, + base - 10 * WEEK_IN_MILLIS, base, 0, WEEK_IN_MILLIS, 0)); + + assertEquals("January 6", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); + assertEquals("January 6", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); + assertEquals("January 6, 2018", getRelativeTimeSpanString(en_US, tz, + base - 30 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); + assertEquals("December 7, 2017", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, 0)); + assertEquals("December 7, 2017", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_SHOW_YEAR)); + assertEquals("December 7", getRelativeTimeSpanString(en_US, tz, + base - 60 * DAY_IN_MILLIS, base, DAY_IN_MILLIS, FORMAT_NO_YEAR)); + } + + // Check for missing ICU data. http://b/25821045 + @Test + public void test_bug25821045() { + final TimeZone tz = TimeZone.getDefault(); + final long now = System.currentTimeMillis(); + final long time = now + 1000; + final int minResolution = 1000 * 60; + final int transitionResolution = minResolution; + final int flags = FORMAT_ABBREV_RELATIVE; + // Exercise all available locales, forcing the ICU implementation to pre-cache the data. + // This + // highlights data issues. It can take a while. + for (Locale locale : Locale.getAvailableLocales()) { + // In (e.g.) ICU56 an exception is thrown on the first use for a locale if required + // data for + // the "other" plural is missing. It doesn't matter what is actually formatted. + try { + RelativeDateTimeFormatter.getRelativeDateTimeString( + locale, tz, time, now, minResolution, transitionResolution, flags); + } catch (IllegalStateException e) { + fail("Failed to format for " + locale); + } + } + } + + // Check for ICU data lookup fallback failure. http://b/25883157 + @Test + public void test_bug25883157() { + final Locale locale = new Locale("en", "GB"); + final TimeZone tz = TimeZone.getTimeZone("GMT"); + + final Calendar cal = Calendar.getInstance(tz, locale); + cal.set(2015, Calendar.JUNE, 19, 12, 0, 0); + + final long base = cal.getTimeInMillis(); + final long time = base + 2 * WEEK_IN_MILLIS; + + assertEquals("In 2 wk", getRelativeTimeSpanString( + locale, tz, time, base, WEEK_IN_MILLIS, FORMAT_ABBREV_RELATIVE)); + } + + // http://b/63745717 + @Test + public void test_combineDateAndTime_apostrophe() { + final Locale locale = new Locale("fr"); + android.icu.text.RelativeDateTimeFormatter icuFormatter = + android.icu.text.RelativeDateTimeFormatter.getInstance(locale); + assertEquals("D à T", icuFormatter.combineDateAndTime("D", "T")); + // Ensure single quote ' and curly braces {} are not interpreted in input values. + assertEquals("D'x' à T{0}", icuFormatter.combineDateAndTime("D'x'", "T{0}")); + } +} diff --git a/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java b/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java index 76aa93f7e8be..edf473eac1b1 100644 --- a/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java +++ b/core/tests/utiltests/src/com/android/internal/util/StateMachineTest.java @@ -16,27 +16,25 @@ package com.android.internal.util; -import java.util.Collection; -import java.util.Iterator; - import android.os.Debug; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.SystemClock; import android.os.test.TestLooper; - -import android.test.suitebuilder.annotation.Suppress; -import com.android.internal.util.State; -import com.android.internal.util.StateMachine; -import com.android.internal.util.StateMachine.LogRec; - import android.test.suitebuilder.annotation.MediumTest; import android.test.suitebuilder.annotation.SmallTest; import android.util.Log; +import com.android.internal.util.StateMachine.LogRec; + import junit.framework.TestCase; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Collection; +import java.util.Iterator; + /** * Test for StateMachine. */ @@ -2013,4 +2011,12 @@ public class StateMachineTest extends TestCase { private static void tloge(String s) { Log.e(TAG, s); } + + public void testDumpDoesNotThrowNpeAfterQuit() { + final Hsm1 sm = Hsm1.makeHsm1(); + sm.quitNow(); + final StringWriter stringWriter = new StringWriter(); + final PrintWriter printWriter = new PrintWriter(stringWriter); + sm.dump(null, printWriter, new String[0]); + } } diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index 5c2ef15cbd1e..5e480a66c355 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -24,37 +24,10 @@ #include <log/log.h> -#include <SkBitmap.h> -#include <SkCanvas.h> -#include <SkColor.h> -#include <SkPaint.h> -#include <SkBlendMode.h> +#include <memory> namespace android { -// --- WeakLooperCallback --- - -class WeakLooperCallback: public LooperCallback { -protected: - virtual ~WeakLooperCallback() { } - -public: - explicit WeakLooperCallback(const wp<LooperCallback>& callback) : - mCallback(callback) { - } - - virtual int handleEvent(int fd, int events, void* data) { - sp<LooperCallback> callback = mCallback.promote(); - if (callback != NULL) { - return callback->handleEvent(fd, events, data); - } - return 0; // the client is gone, remove the callback - } - -private: - wp<LooperCallback> mCallback; -}; - // --- PointerController --- // Time to wait before starting the fade when the pointer is inactive. @@ -70,29 +43,50 @@ static const nsecs_t POINTER_FADE_DURATION = 500 * 1000000LL; // 500 ms // The number of events to be read at once for DisplayEventReceiver. static const int EVENT_BUFFER_SIZE = 100; -// --- PointerController --- - -PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy, - const sp<Looper>& looper, const sp<SpriteController>& spriteController) : - mPolicy(policy), mLooper(looper), mSpriteController(spriteController) { - mHandler = new WeakMessageHandler(this); - mCallback = new WeakLooperCallback(this); - - if (mDisplayEventReceiver.initCheck() == NO_ERROR) { - mLooper->addFd(mDisplayEventReceiver.getFd(), Looper::POLL_CALLBACK, - Looper::EVENT_INPUT, mCallback, nullptr); +std::shared_ptr<PointerController> PointerController::create( + const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, + const sp<SpriteController>& spriteController) { + std::shared_ptr<PointerController> controller = std::shared_ptr<PointerController>( + new PointerController(policy, looper, spriteController)); + + /* + * Now we need to hook up the constructed PointerController object to its callbacks. + * + * This must be executed after the constructor but before any other methods on PointerController + * in order to ensure that the fully constructed object is visible on the Looper thread, since + * that may be a different thread than where the PointerController is initially constructed. + * + * Unfortunately, this cannot be done as part of the constructor since we need to hand out + * weak_ptr's which themselves cannot be constructed until there's at least one shared_ptr. + */ + + controller->mHandler->pointerController = controller; + controller->mCallback->pointerController = controller; + if (controller->mDisplayEventReceiver.initCheck() == NO_ERROR) { + controller->mLooper->addFd(controller->mDisplayEventReceiver.getFd(), Looper::POLL_CALLBACK, + Looper::EVENT_INPUT, controller->mCallback, nullptr); } else { ALOGE("Failed to initialize DisplayEventReceiver."); } + return controller; +} +PointerController::PointerController(const sp<PointerControllerPolicyInterface>& policy, + const sp<Looper>& looper, + const sp<SpriteController>& spriteController) + : mPolicy(policy), + mLooper(looper), + mSpriteController(spriteController), + mHandler(new MessageHandler()), + mCallback(new LooperCallback()) { AutoMutex _l(mLock); mLocked.animationPending = false; - mLocked.presentation = PRESENTATION_POINTER; + mLocked.presentation = Presentation::POINTER; mLocked.presentationChanged = false; - mLocked.inactivityTimeout = INACTIVITY_TIMEOUT_NORMAL; + mLocked.inactivityTimeout = InactivityTimeout::NORMAL; mLocked.pointerFadeDirection = 0; mLocked.pointerX = 0; @@ -227,7 +221,7 @@ void PointerController::fade(Transition transition) { removeInactivityTimeoutLocked(); // Start fading. - if (transition == TRANSITION_IMMEDIATE) { + if (transition == Transition::IMMEDIATE) { mLocked.pointerFadeDirection = 0; mLocked.pointerAlpha = 0.0f; updatePointerLocked(); @@ -244,7 +238,7 @@ void PointerController::unfade(Transition transition) { resetInactivityTimeoutLocked(); // Start unfading. - if (transition == TRANSITION_IMMEDIATE) { + if (transition == Transition::IMMEDIATE) { mLocked.pointerFadeDirection = 0; mLocked.pointerAlpha = 1.0f; updatePointerLocked(); @@ -268,7 +262,7 @@ void PointerController::setPresentation(Presentation presentation) { return; } - if (presentation == PRESENTATION_POINTER) { + if (presentation == Presentation::POINTER) { if (mLocked.additionalMouseResources.empty()) { mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources, &mLocked.animationResources, @@ -486,24 +480,35 @@ void PointerController::setCustomPointerIcon(const SpriteIcon& icon) { updatePointerLocked(); } -void PointerController::handleMessage(const Message& message) { +void PointerController::MessageHandler::handleMessage(const Message& message) { + std::shared_ptr<PointerController> controller = pointerController.lock(); + + if (controller == nullptr) { + ALOGE("PointerController instance was released before processing message: what=%d", + message.what); + return; + } switch (message.what) { case MSG_INACTIVITY_TIMEOUT: - doInactivityTimeout(); + controller->doInactivityTimeout(); break; } } -int PointerController::handleEvent(int /* fd */, int events, void* /* data */) { +int PointerController::LooperCallback::handleEvent(int /* fd */, int events, void* /* data */) { + std::shared_ptr<PointerController> controller = pointerController.lock(); + if (controller == nullptr) { + ALOGW("PointerController instance was released with pending callbacks. events=0x%x", + events); + return 0; // Remove the callback, the PointerController is gone anyways + } if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) { - ALOGE("Display event receiver pipe was closed or an error occurred. " - "events=0x%x", events); + ALOGE("Display event receiver pipe was closed or an error occurred. events=0x%x", events); return 0; // remove the callback } if (!(events & Looper::EVENT_INPUT)) { - ALOGW("Received spurious callback for unhandled poll event. " - "events=0x%x", events); + ALOGW("Received spurious callback for unhandled poll event. events=0x%x", events); return 1; // keep the callback } @@ -511,7 +516,7 @@ int PointerController::handleEvent(int /* fd */, int events, void* /* data */) { ssize_t n; nsecs_t timestamp; DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE]; - while ((n = mDisplayEventReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) { + while ((n = controller->mDisplayEventReceiver.getEvents(buf, EVENT_BUFFER_SIZE)) > 0) { for (size_t i = 0; i < static_cast<size_t>(n); ++i) { if (buf[i].header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) { timestamp = buf[i].header.timestamp; @@ -520,7 +525,7 @@ int PointerController::handleEvent(int /* fd */, int events, void* /* data */) { } } if (gotVsync) { - doAnimate(timestamp); + controller->doAnimate(timestamp); } return 1; // keep the callback } @@ -619,7 +624,7 @@ bool PointerController::doBitmapAnimationLocked(nsecs_t timestamp) { } void PointerController::doInactivityTimeout() { - fade(TRANSITION_GRADUAL); + fade(Transition::GRADUAL); } void PointerController::startAnimationLocked() { @@ -633,8 +638,9 @@ void PointerController::startAnimationLocked() { void PointerController::resetInactivityTimeoutLocked() { mLooper->removeMessages(mHandler, MSG_INACTIVITY_TIMEOUT); - nsecs_t timeout = mLocked.inactivityTimeout == INACTIVITY_TIMEOUT_SHORT - ? INACTIVITY_TIMEOUT_DELAY_TIME_SHORT : INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL; + nsecs_t timeout = mLocked.inactivityTimeout == InactivityTimeout::SHORT + ? INACTIVITY_TIMEOUT_DELAY_TIME_SHORT + : INACTIVITY_TIMEOUT_DELAY_TIME_NORMAL; mLooper->sendMessageDelayed(timeout, mHandler, MSG_INACTIVITY_TIMEOUT); } @@ -661,7 +667,7 @@ void PointerController::updatePointerLocked() REQUIRES(mLock) { } if (mLocked.pointerIconChanged || mLocked.presentationChanged) { - if (mLocked.presentation == PRESENTATION_POINTER) { + if (mLocked.presentation == Presentation::POINTER) { if (mLocked.requestedPointerType == mPolicy->getDefaultPointerIconId()) { mLocked.pointerSprite->setIcon(mLocked.pointerIcon); } else { @@ -737,7 +743,7 @@ PointerController::Spot* PointerController::removeFirstFadingSpotLocked(std::vec return spot; } } - return NULL; + return nullptr; } void PointerController::releaseSpotLocked(Spot* spot) { @@ -778,7 +784,7 @@ void PointerController::loadResourcesLocked() REQUIRES(mLock) { mLocked.additionalMouseResources.clear(); mLocked.animationResources.clear(); - if (mLocked.presentation == PRESENTATION_POINTER) { + if (mLocked.presentation == Presentation::POINTER) { mPolicy->loadAdditionalMouseResources(&mLocked.additionalMouseResources, &mLocked.animationResources, mLocked.viewport.displayId); } diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index ebc622bae302..14c0679654c6 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -17,19 +17,20 @@ #ifndef _UI_POINTER_CONTROLLER_H #define _UI_POINTER_CONTROLLER_H -#include "SpriteController.h" - -#include <map> -#include <vector> - -#include <ui/DisplayInfo.h> +#include <PointerControllerInterface.h> +#include <gui/DisplayEventReceiver.h> #include <input/DisplayViewport.h> #include <input/Input.h> -#include <PointerControllerInterface.h> +#include <ui/DisplayInfo.h> #include <utils/BitSet.h> -#include <utils/RefBase.h> #include <utils/Looper.h> -#include <gui/DisplayEventReceiver.h> +#include <utils/RefBase.h> + +#include <map> +#include <memory> +#include <vector> + +#include "SpriteController.h" namespace android { @@ -70,25 +71,22 @@ public: virtual int32_t getCustomPointerIconId() = 0; }; - /* * Tracks pointer movements and draws the pointer sprite to a surface. * * Handles pointer acceleration and animation. */ -class PointerController : public PointerControllerInterface, public MessageHandler, - public LooperCallback { -protected: - virtual ~PointerController(); - +class PointerController : public PointerControllerInterface { public: - enum InactivityTimeout { - INACTIVITY_TIMEOUT_NORMAL = 0, - INACTIVITY_TIMEOUT_SHORT = 1, + static std::shared_ptr<PointerController> create( + const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, + const sp<SpriteController>& spriteController); + enum class InactivityTimeout { + NORMAL = 0, + SHORT = 1, }; - PointerController(const sp<PointerControllerPolicyInterface>& policy, - const sp<Looper>& looper, const sp<SpriteController>& spriteController); + virtual ~PointerController(); virtual bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const; @@ -113,8 +111,8 @@ public: void reloadPointerResources(); private: - static const size_t MAX_RECYCLED_SPRITES = 12; - static const size_t MAX_SPOTS = 12; + static constexpr size_t MAX_RECYCLED_SPRITES = 12; + static constexpr size_t MAX_SPOTS = 12; enum { MSG_INACTIVITY_TIMEOUT, @@ -130,8 +128,13 @@ private: float x, y; inline Spot(uint32_t id, const sp<Sprite>& sprite) - : id(id), sprite(sprite), alpha(1.0f), scale(1.0f), - x(0.0f), y(0.0f), lastIcon(NULL) { } + : id(id), + sprite(sprite), + alpha(1.0f), + scale(1.0f), + x(0.0f), + y(0.0f), + lastIcon(nullptr) {} void updateSprite(const SpriteIcon* icon, float x, float y, int32_t displayId); @@ -139,12 +142,24 @@ private: const SpriteIcon* lastIcon; }; + class MessageHandler : public virtual android::MessageHandler { + public: + void handleMessage(const Message& message) override; + std::weak_ptr<PointerController> pointerController; + }; + + class LooperCallback : public virtual android::LooperCallback { + public: + int handleEvent(int fd, int events, void* data) override; + std::weak_ptr<PointerController> pointerController; + }; + mutable Mutex mLock; sp<PointerControllerPolicyInterface> mPolicy; sp<Looper> mLooper; sp<SpriteController> mSpriteController; - sp<WeakMessageHandler> mHandler; + sp<MessageHandler> mHandler; sp<LooperCallback> mCallback; DisplayEventReceiver mDisplayEventReceiver; @@ -181,14 +196,15 @@ private: int32_t buttonState; std::map<int32_t /* displayId */, std::vector<Spot*>> spotsByDisplay; - std::vector<sp<Sprite> > recycledSprites; + std::vector<sp<Sprite>> recycledSprites; } mLocked GUARDED_BY(mLock); + PointerController(const sp<PointerControllerPolicyInterface>& policy, const sp<Looper>& looper, + const sp<SpriteController>& spriteController); + bool getBoundsLocked(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const; void setPositionLocked(float x, float y); - void handleMessage(const Message& message); - int handleEvent(int fd, int events, void* data); void doAnimate(nsecs_t timestamp); bool doFadingAnimationLocked(nsecs_t timestamp); bool doBitmapAnimationLocked(nsecs_t timestamp); diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp index a15742671dc7..6e129a064385 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -136,7 +136,7 @@ protected: sp<MockSprite> mPointerSprite; sp<MockPointerControllerPolicyInterface> mPolicy; sp<MockSpriteController> mSpriteController; - sp<PointerController> mPointerController; + std::shared_ptr<PointerController> mPointerController; private: void loopThread(); @@ -160,7 +160,7 @@ PointerControllerTest::PointerControllerTest() : mPointerSprite(new NiceMock<Moc EXPECT_CALL(*mSpriteController, createSprite()) .WillOnce(Return(mPointerSprite)); - mPointerController = new PointerController(mPolicy, mLooper, mSpriteController); + mPointerController = PointerController::create(mPolicy, mLooper, mSpriteController); } PointerControllerTest::~PointerControllerTest() { @@ -193,7 +193,7 @@ void PointerControllerTest::loopThread() { TEST_F(PointerControllerTest, useDefaultCursorTypeByDefault) { ensureDisplayViewportIsSet(); - mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE); + mPointerController->unfade(PointerController::Transition::IMMEDIATE); std::pair<float, float> hotspot = getHotSpotCoordinatesForType(CURSOR_TYPE_DEFAULT); EXPECT_CALL(*mPointerSprite, setVisible(true)); @@ -208,7 +208,7 @@ TEST_F(PointerControllerTest, useDefaultCursorTypeByDefault) { TEST_F(PointerControllerTest, updatePointerIcon) { ensureDisplayViewportIsSet(); - mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE); + mPointerController->unfade(PointerController::Transition::IMMEDIATE); int32_t type = CURSOR_TYPE_ADDITIONAL; std::pair<float, float> hotspot = getHotSpotCoordinatesForType(type); @@ -224,7 +224,7 @@ TEST_F(PointerControllerTest, updatePointerIcon) { TEST_F(PointerControllerTest, setCustomPointerIcon) { ensureDisplayViewportIsSet(); - mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE); + mPointerController->unfade(PointerController::Transition::IMMEDIATE); int32_t style = CURSOR_TYPE_CUSTOM; float hotSpotX = 15; @@ -246,13 +246,13 @@ TEST_F(PointerControllerTest, setCustomPointerIcon) { } TEST_F(PointerControllerTest, doesNotGetResourcesBeforeSettingViewport) { - mPointerController->setPresentation(PointerController::PRESENTATION_POINTER); + mPointerController->setPresentation(PointerController::Presentation::POINTER); mPointerController->setSpots(nullptr, nullptr, BitSet32(), -1); mPointerController->clearSpots(); mPointerController->setPosition(1.0f, 1.0f); mPointerController->move(1.0f, 1.0f); - mPointerController->unfade(PointerController::TRANSITION_IMMEDIATE); - mPointerController->fade(PointerController::TRANSITION_IMMEDIATE); + mPointerController->unfade(PointerController::Transition::IMMEDIATE); + mPointerController->fade(PointerController::Transition::IMMEDIATE); EXPECT_TRUE(mPolicy->noResourcesAreLoaded()); diff --git a/non-updatable-api/current.txt b/non-updatable-api/current.txt index 3ad076f60c08..a28569297832 100644 --- a/non-updatable-api/current.txt +++ b/non-updatable-api/current.txt @@ -44999,7 +44999,7 @@ package android.telephony { public abstract class CellLocation { ctor public CellLocation(); method public static android.telephony.CellLocation getEmpty(); - method public static void requestLocationUpdate(); + method @Deprecated public static void requestLocationUpdate(); } public abstract class CellSignalStrength { diff --git a/packages/Tethering/common/TetheringLib/api/module-lib-current.txt b/packages/Tethering/common/TetheringLib/api/module-lib-current.txt index 754584e70fad..6ddb122936e7 100644 --- a/packages/Tethering/common/TetheringLib/api/module-lib-current.txt +++ b/packages/Tethering/common/TetheringLib/api/module-lib-current.txt @@ -1,24 +1,6 @@ // Signature format: 2.0 package android.net { - public final class TetheredClient implements android.os.Parcelable { - ctor public TetheredClient(@NonNull android.net.MacAddress, @NonNull java.util.Collection<android.net.TetheredClient.AddressInfo>, int); - method public int describeContents(); - method @NonNull public java.util.List<android.net.TetheredClient.AddressInfo> getAddresses(); - method @NonNull public android.net.MacAddress getMacAddress(); - method public int getTetheringType(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheredClient> CREATOR; - } - - public static final class TetheredClient.AddressInfo implements android.os.Parcelable { - method public int describeContents(); - method @NonNull public android.net.LinkAddress getAddress(); - method @Nullable public String getHostname(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.net.TetheredClient.AddressInfo> CREATOR; - } - public final class TetheringConstants { field public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType"; field public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback"; @@ -38,69 +20,15 @@ package android.net { method @NonNull public String[] getTetheringErroredIfaces(); method public boolean isTetheringSupported(); method public boolean isTetheringSupported(@NonNull String); - method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.TetheringEventCallback); - method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void requestLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.OnTetheringEntitlementResultListener); method public void requestLatestTetheringEntitlementResult(int, @NonNull android.os.ResultReceiver, boolean); method @Deprecated public int setUsbTethering(boolean); - method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(@NonNull android.net.TetheringManager.TetheringRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback); method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void startTethering(int, @NonNull java.util.concurrent.Executor, @NonNull android.net.TetheringManager.StartTetheringCallback); - method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void stopAllTethering(); - method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public void stopTethering(int); method @Deprecated public int tether(@NonNull String); - method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.ACCESS_NETWORK_STATE}) public void unregisterTetheringEventCallback(@NonNull android.net.TetheringManager.TetheringEventCallback); method @Deprecated public int untether(@NonNull String); - field public static final String ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED"; - field public static final String EXTRA_ACTIVE_LOCAL_ONLY = "android.net.extra.ACTIVE_LOCAL_ONLY"; - field public static final String EXTRA_ACTIVE_TETHER = "tetherArray"; - field public static final String EXTRA_AVAILABLE_TETHER = "availableArray"; - field public static final String EXTRA_ERRORED_TETHER = "erroredArray"; - field public static final int TETHERING_BLUETOOTH = 2; // 0x2 - field public static final int TETHERING_ETHERNET = 5; // 0x5 - field public static final int TETHERING_INVALID = -1; // 0xffffffff - field public static final int TETHERING_NCM = 4; // 0x4 - field public static final int TETHERING_USB = 1; // 0x1 - field public static final int TETHERING_WIFI = 0; // 0x0 - field public static final int TETHERING_WIFI_P2P = 3; // 0x3 - field public static final int TETHER_ERROR_DHCPSERVER_ERROR = 12; // 0xc - field public static final int TETHER_ERROR_DISABLE_FORWARDING_ERROR = 9; // 0x9 - field public static final int TETHER_ERROR_ENABLE_FORWARDING_ERROR = 8; // 0x8 - field public static final int TETHER_ERROR_ENTITLEMENT_UNKNOWN = 13; // 0xd - field public static final int TETHER_ERROR_IFACE_CFG_ERROR = 10; // 0xa - field public static final int TETHER_ERROR_INTERNAL_ERROR = 5; // 0x5 - field public static final int TETHER_ERROR_NO_ACCESS_TETHERING_PERMISSION = 15; // 0xf - field public static final int TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION = 14; // 0xe - field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0 - field public static final int TETHER_ERROR_PROVISIONING_FAILED = 11; // 0xb - field public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2; // 0x2 - field public static final int TETHER_ERROR_TETHER_IFACE_ERROR = 6; // 0x6 - field public static final int TETHER_ERROR_UNAVAIL_IFACE = 4; // 0x4 - field public static final int TETHER_ERROR_UNKNOWN_IFACE = 1; // 0x1 - field public static final int TETHER_ERROR_UNKNOWN_TYPE = 16; // 0x10 - field public static final int TETHER_ERROR_UNSUPPORTED = 3; // 0x3 - field public static final int TETHER_ERROR_UNTETHER_IFACE_ERROR = 7; // 0x7 - field public static final int TETHER_HARDWARE_OFFLOAD_FAILED = 2; // 0x2 - field public static final int TETHER_HARDWARE_OFFLOAD_STARTED = 1; // 0x1 - field public static final int TETHER_HARDWARE_OFFLOAD_STOPPED = 0; // 0x0 - } - - public static interface TetheringManager.OnTetheringEntitlementResultListener { - method public void onTetheringEntitlementResult(int); - } - - public static interface TetheringManager.StartTetheringCallback { - method public default void onTetheringFailed(int); - method public default void onTetheringStarted(); } public static interface TetheringManager.TetheringEventCallback { - method public default void onClientsChanged(@NonNull java.util.Collection<android.net.TetheredClient>); - method public default void onError(@NonNull String, int); - method public default void onOffloadStatusChanged(int); method public default void onTetherableInterfaceRegexpsChanged(@NonNull android.net.TetheringManager.TetheringInterfaceRegexps); - method public default void onTetherableInterfacesChanged(@NonNull java.util.List<java.lang.String>); - method public default void onTetheredInterfacesChanged(@NonNull java.util.List<java.lang.String>); - method public default void onTetheringSupported(boolean); - method public default void onUpstreamChanged(@Nullable android.net.Network); } public static class TetheringManager.TetheringInterfaceRegexps { @@ -109,21 +37,5 @@ package android.net { method @NonNull public java.util.List<java.lang.String> getTetherableWifiRegexs(); } - public static class TetheringManager.TetheringRequest { - method @Nullable public android.net.LinkAddress getClientStaticIpv4Address(); - method @Nullable public android.net.LinkAddress getLocalIpv4Address(); - method public boolean getShouldShowEntitlementUi(); - method public int getTetheringType(); - method public boolean isExemptFromEntitlementCheck(); - } - - public static class TetheringManager.TetheringRequest.Builder { - ctor public TetheringManager.TetheringRequest.Builder(int); - method @NonNull public android.net.TetheringManager.TetheringRequest build(); - method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setExemptFromEntitlementCheck(boolean); - method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setShouldShowEntitlementUi(boolean); - method @NonNull @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public android.net.TetheringManager.TetheringRequest.Builder setStaticIpv4Addresses(@NonNull android.net.LinkAddress, @NonNull android.net.LinkAddress); - } - } diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java index 48be0d96425c..645b00001330 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheredClient.java @@ -16,8 +16,6 @@ package android.net; -import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; - import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; @@ -36,7 +34,6 @@ import java.util.Objects; * @hide */ @SystemApi -@SystemApi(client = MODULE_LIBRARIES) @TestApi public final class TetheredClient implements Parcelable { @NonNull diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java index 87e5c1e52198..49c7fe20660d 100644 --- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java +++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java @@ -55,7 +55,6 @@ import java.util.function.Supplier; * @hide */ @SystemApi -@SystemApi(client = MODULE_LIBRARIES) @TestApi public class TetheringManager { private static final String TAG = TetheringManager.class.getSimpleName(); diff --git a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java index 74df11370e50..9bb01ae5df1d 100644 --- a/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java +++ b/packages/Tethering/tests/integration/src/android/net/EthernetTetheringTest.java @@ -42,6 +42,7 @@ import android.net.dhcp.DhcpPacket; import android.os.Handler; import android.os.HandlerThread; import android.os.SystemClock; +import android.os.SystemProperties; import android.system.Os; import android.util.Log; @@ -101,17 +102,21 @@ public class EthernetTetheringTest { private UiAutomation mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); + private boolean mRunTests; @Before public void setUp() throws Exception { - mHandlerThread = new HandlerThread(getClass().getSimpleName()); - mHandlerThread.start(); - mHandler = new Handler(mHandlerThread.getLooper()); - mTetheredInterfaceRequester = new TetheredInterfaceRequester(mHandler, mEm); // Needed to create a TestNetworkInterface, to call requestTetheredInterface, and to receive // tethered client callbacks. mUiAutomation.adoptShellPermissionIdentity( MANAGE_TEST_NETWORKS, NETWORK_SETTINGS, TETHER_PRIVILEGED); + mRunTests = mTm.isTetheringSupported() && mEm != null; + assumeTrue(mRunTests); + + mHandlerThread = new HandlerThread(getClass().getSimpleName()); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + mTetheredInterfaceRequester = new TetheredInterfaceRequester(mHandler, mEm); } private void cleanUp() throws Exception { @@ -135,7 +140,7 @@ public class EthernetTetheringTest { @After public void tearDown() throws Exception { try { - cleanUp(); + if (mRunTests) cleanUp(); } finally { mUiAutomation.dropShellPermissionIdentity(); } @@ -224,9 +229,19 @@ public class EthernetTetheringTest { } + private boolean isAdbOverNetwork() { + // If adb TCP port opened, this test may running by adb over network. + return (SystemProperties.getInt("persist.adb.tcp.port", -1) > -1) + || (SystemProperties.getInt("service.adb.tcp.port", -1) > -1); + } + @Test public void testPhysicalEthernet() throws Exception { assumeTrue(mEm.isAvailable()); + // Do not run this test if adb is over network and ethernet is connected. + // It is likely the adb run over ethernet, the adb would break when ethernet is switching + // from client mode to server mode. See b/160389275. + assumeFalse(isAdbOverNetwork()); // Get an interface to use. final String iface = mTetheredInterfaceRequester.getInterface(); diff --git a/packages/Tethering/tests/privileged/Android.bp b/packages/Tethering/tests/privileged/Android.bp new file mode 100644 index 000000000000..a0fb24603a93 --- /dev/null +++ b/packages/Tethering/tests/privileged/Android.bp @@ -0,0 +1,30 @@ +// +// Copyright (C) 2020 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. +// + +android_test { + name: "TetheringPrivilegedTests", + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + certificate: "networkstack", + platform_apis: true, + test_suites: [ + "general-tests", + "mts", + ], + compile_multilib: "both", +} diff --git a/packages/Tethering/tests/privileged/AndroidManifest.xml b/packages/Tethering/tests/privileged/AndroidManifest.xml new file mode 100644 index 000000000000..49eba15d13d4 --- /dev/null +++ b/packages/Tethering/tests/privileged/AndroidManifest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2020 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. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.networkstack.tethering.tests.privileged" + android:sharedUserId="android.uid.networkstack"> + + <!-- Note: do not add any privileged or signature permissions that are granted + to the network stack and its shared uid apps. Otherwise, the test APK will + install, but when the device is rebooted, it will bootloop because this + test APK is not in the privileged permission allow list --> + + <application android:debuggable="true"> + <uses-library android:name="android.test.runner" /> + </application> + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.networkstack.tethering.tests.privileged" + android:label="Tethering privileged tests"> + </instrumentation> +</manifest> diff --git a/packages/services/PacProcessor/src/com/android/net/IProxyService.aidl b/packages/services/PacProcessor/src/com/android/net/IProxyService.aidl index 4e54aba5c3bf..1bbc90d604f9 100644 --- a/packages/services/PacProcessor/src/com/android/net/IProxyService.aidl +++ b/packages/services/PacProcessor/src/com/android/net/IProxyService.aidl @@ -21,7 +21,4 @@ interface IProxyService String resolvePacFile(String host, String url); oneway void setPacFile(String scriptContents); - - oneway void startPacSystem(); - oneway void stopPacSystem(); } diff --git a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java index b006d6e1fa7b..3c25bfd380f2 100644 --- a/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java +++ b/packages/services/PacProcessor/src/com/android/pacprocessor/PacService.java @@ -83,15 +83,5 @@ public class PacService extends Service { } mPacNative.setCurrentProxyScript(script); } - - @Override - public void startPacSystem() throws RemoteException { - //TODO: remove - } - - @Override - public void stopPacSystem() throws RemoteException { - //TODO: remove - } } } diff --git a/services/backup/OWNERS b/services/backup/OWNERS index ba61d1c0849b..7c7e74285bf5 100644 --- a/services/backup/OWNERS +++ b/services/backup/OWNERS @@ -1,7 +1,12 @@ # Bug component: 656484 +aabhinav@google.com alsutton@google.com bryanmawhinney@google.com +jstemmer@google.com nathch@google.com +niagra@google.com +niamhfw@google.com +philippov@google.com rthakohov@google.com tobiast@google.com diff --git a/services/core/java/com/android/server/connectivity/PacManager.java b/services/core/java/com/android/server/connectivity/PacManager.java index f6ce2dc68b99..de302fc01f2d 100644 --- a/services/core/java/com/android/server/connectivity/PacManager.java +++ b/services/core/java/com/android/server/connectivity/PacManager.java @@ -196,13 +196,7 @@ public class PacManager { mPacUrl = Uri.EMPTY; mCurrentPac = null; if (mProxyService != null) { - try { - mProxyService.stopPacSystem(); - } catch (RemoteException e) { - Log.w(TAG, "Failed to stop PAC service", e); - } finally { - unbind(); - } + unbind(); } } return DO_SEND_BROADCAST; @@ -327,11 +321,6 @@ public class PacManager { if (mProxyService == null) { Log.e(TAG, "No proxy service"); } else { - try { - mProxyService.startPacSystem(); - } catch (RemoteException e) { - Log.e(TAG, "Unable to reach ProxyService - PAC will not be started", e); - } mNetThreadHandler.post(mPacDownloader); } } diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java index c5aa8d5361ec..a75a80a606eb 100644 --- a/services/core/java/com/android/server/connectivity/PermissionMonitor.java +++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java @@ -82,6 +82,7 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse private final PackageManager mPackageManager; private final UserManager mUserManager; private final INetd mNetd; + private final Dependencies mDeps; // Values are User IDs. @GuardedBy("this") @@ -102,10 +103,30 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse @GuardedBy("this") private final Set<Integer> mAllApps = new HashSet<>(); - public PermissionMonitor(Context context, INetd netd) { + /** + * Dependencies of PermissionMonitor, for injection in tests. + */ + @VisibleForTesting + public static class Dependencies { + /** + * Get device first sdk version. + */ + public int getDeviceFirstSdkInt() { + return Build.VERSION.FIRST_SDK_INT; + } + } + + public PermissionMonitor(@NonNull final Context context, @NonNull final INetd netd) { + this(context, netd, new Dependencies()); + } + + @VisibleForTesting + PermissionMonitor(@NonNull final Context context, @NonNull final INetd netd, + @NonNull final Dependencies deps) { mPackageManager = context.getPackageManager(); mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); mNetd = netd; + mDeps = deps; } // Intended to be called only once at startup, after the system is ready. Installs a broadcast @@ -186,11 +207,6 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse } @VisibleForTesting - protected int getDeviceFirstSdkInt() { - return Build.VERSION.FIRST_SDK_INT; - } - - @VisibleForTesting boolean hasPermission(@NonNull final PackageInfo app, @NonNull final String permission) { if (app.requestedPermissions == null || app.requestedPermissionsFlags == null) { return false; @@ -212,7 +228,7 @@ public class PermissionMonitor implements PackageManagerInternal.PackageListObse if (app.applicationInfo != null) { // Backward compatibility for b/114245686, on devices that launched before Q daemons // and apps running as the system UID are exempted from this check. - if (app.applicationInfo.uid == SYSTEM_UID && getDeviceFirstSdkInt() < VERSION_Q) { + if (app.applicationInfo.uid == SYSTEM_UID && mDeps.getDeviceFirstSdkInt() < VERSION_Q) { return true; } diff --git a/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java index 7711c6a21d20..5f2c4a38b42a 100644 --- a/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java +++ b/services/core/java/com/android/server/net/NetworkStatsSubscriptionsMonitor.java @@ -16,12 +16,14 @@ package com.android.server.net; +import static android.net.NetworkTemplate.NETWORK_TYPE_5G_NSA; import static android.net.NetworkTemplate.getCollapsedRatType; import android.annotation.NonNull; import android.content.Context; import android.os.Looper; import android.telephony.Annotation; +import android.telephony.NetworkRegistrationInfo; import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.SubscriptionManager; @@ -196,7 +198,18 @@ public class NetworkStatsSubscriptionsMonitor extends @Override public void onServiceStateChanged(@NonNull ServiceState ss) { - final int networkType = ss.getDataNetworkType(); + // In 5G SA (Stand Alone) mode, the primary cell itself will be 5G hence telephony + // would report RAT = 5G_NR. + // However, in 5G NSA (Non Stand Alone) mode, the primary cell is still LTE and + // network allocates a secondary 5G cell so telephony reports RAT = LTE along with + // NR state as connected. In such case, attributes the data usage to NR. + // See b/160727498. + final boolean is5GNsa = (ss.getDataNetworkType() == TelephonyManager.NETWORK_TYPE_LTE + || ss.getDataNetworkType() == TelephonyManager.NETWORK_TYPE_LTE_CA) + && ss.getNrState() == NetworkRegistrationInfo.NR_STATE_CONNECTED; + + final int networkType = + (is5GNsa ? NETWORK_TYPE_5G_NSA : ss.getDataNetworkType()); final int collapsedRatType = getCollapsedRatType(networkType); if (collapsedRatType == mLastCollapsedRatType) return; diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 7aaa0745e5c6..1f445c9ca3bd 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -233,7 +233,7 @@ public: /* --- InputReaderPolicyInterface implementation --- */ virtual void getReaderConfiguration(InputReaderConfiguration* outConfig); - virtual sp<PointerControllerInterface> obtainPointerController(int32_t deviceId); + virtual std::shared_ptr<PointerControllerInterface> obtainPointerController(int32_t deviceId); virtual void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices); virtual sp<KeyCharacterMap> getKeyboardLayoutOverlay(const InputDeviceIdentifier& identifier); virtual std::string getDeviceAlias(const InputDeviceIdentifier& identifier); @@ -306,7 +306,7 @@ private: sp<SpriteController> spriteController; // Pointer controller singleton, created and destroyed as needed. - wp<PointerController> pointerController; + std::weak_ptr<PointerController> pointerController; // Input devices to be disabled SortedVector<int32_t> disabledInputDevices; @@ -551,15 +551,16 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon } // release lock } -sp<PointerControllerInterface> NativeInputManager::obtainPointerController(int32_t /* deviceId */) { +std::shared_ptr<PointerControllerInterface> NativeInputManager::obtainPointerController( + int32_t /* deviceId */) { ATRACE_CALL(); AutoMutex _l(mLock); - sp<PointerController> controller = mLocked.pointerController.promote(); + std::shared_ptr<PointerController> controller = mLocked.pointerController.lock(); if (controller == nullptr) { ensureSpriteControllerLocked(); - controller = new PointerController(this, mLooper, mLocked.spriteController); + controller = PointerController::create(this, mLooper, mLocked.spriteController); mLocked.pointerController = controller; updateInactivityTimeoutLocked(); } @@ -840,15 +841,14 @@ void NativeInputManager::setSystemUiVisibility(int32_t visibility) { } void NativeInputManager::updateInactivityTimeoutLocked() REQUIRES(mLock) { - sp<PointerController> controller = mLocked.pointerController.promote(); + std::shared_ptr<PointerController> controller = mLocked.pointerController.lock(); if (controller == nullptr) { return; } bool lightsOut = mLocked.systemUiVisibility & ASYSTEM_UI_VISIBILITY_STATUS_BAR_HIDDEN; - controller->setInactivityTimeout(lightsOut - ? PointerController::INACTIVITY_TIMEOUT_SHORT - : PointerController::INACTIVITY_TIMEOUT_NORMAL); + controller->setInactivityTimeout(lightsOut ? PointerController::InactivityTimeout::SHORT + : PointerController::InactivityTimeout::NORMAL); } void NativeInputManager::setPointerSpeed(int32_t speed) { @@ -928,7 +928,7 @@ void NativeInputManager::reloadCalibration() { void NativeInputManager::setPointerIconType(int32_t iconId) { AutoMutex _l(mLock); - sp<PointerController> controller = mLocked.pointerController.promote(); + std::shared_ptr<PointerController> controller = mLocked.pointerController.lock(); if (controller != nullptr) { controller->updatePointerIcon(iconId); } @@ -936,7 +936,7 @@ void NativeInputManager::setPointerIconType(int32_t iconId) { void NativeInputManager::reloadPointerIcons() { AutoMutex _l(mLock); - sp<PointerController> controller = mLocked.pointerController.promote(); + std::shared_ptr<PointerController> controller = mLocked.pointerController.lock(); if (controller != nullptr) { controller->reloadPointerResources(); } @@ -944,7 +944,7 @@ void NativeInputManager::reloadPointerIcons() { void NativeInputManager::setCustomPointerIcon(const SpriteIcon& icon) { AutoMutex _l(mLock); - sp<PointerController> controller = mLocked.pointerController.promote(); + std::shared_ptr<PointerController> controller = mLocked.pointerController.lock(); if (controller != nullptr) { controller->setCustomPointerIcon(icon); } diff --git a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java index 3c1e707ab1dd..4af19b5489ca 100644 --- a/telephony/common/com/android/internal/telephony/CarrierAppUtils.java +++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java @@ -127,6 +127,14 @@ public final class CarrierAppUtils { return userContext.getContentResolver(); } + private static boolean isUpdatedSystemApp(ApplicationInfo ai) { + if ((ai.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { + return true; + } + + return false; + } + /** * Disable carrier apps until they are privileged * Must be public b/c framework unit tests can't access package-private methods. @@ -138,7 +146,7 @@ public final class CarrierAppUtils { ContentResolver contentResolver, int userId, ArraySet<String> systemCarrierAppsDisabledUntilUsed, ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed) { - List<ApplicationInfo> candidates = getDefaultNotUpdatedCarrierAppCandidatesHelper( + List<ApplicationInfo> candidates = getDefaultCarrierAppCandidatesHelper( packageManager, userId, systemCarrierAppsDisabledUntilUsed); if (candidates == null || candidates.isEmpty()) { return; @@ -180,7 +188,7 @@ public final class CarrierAppUtils { if (hasPrivileges) { // Only update enabled state for the app on /system. Once it has been // updated we shouldn't touch it. - if (enabledSetting + if (!isUpdatedSystemApp(ai) && enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT || enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED @@ -232,7 +240,7 @@ public final class CarrierAppUtils { } else { // No carrier privileges // Only update enabled state for the app on /system. Once it has been // updated we shouldn't touch it. - if (enabledSetting + if (!isUpdatedSystemApp(ai) && enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT && (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) { Log.i(TAG, "Update state(" + packageName @@ -363,31 +371,6 @@ public final class CarrierAppUtils { return apps; } - private static List<ApplicationInfo> getDefaultNotUpdatedCarrierAppCandidatesHelper( - IPackageManager packageManager, - int userId, - ArraySet<String> systemCarrierAppsDisabledUntilUsed) { - if (systemCarrierAppsDisabledUntilUsed == null) { - return null; - } - - int size = systemCarrierAppsDisabledUntilUsed.size(); - if (size == 0) { - return null; - } - - List<ApplicationInfo> apps = new ArrayList<>(size); - for (int i = 0; i < size; i++) { - String packageName = systemCarrierAppsDisabledUntilUsed.valueAt(i); - ApplicationInfo ai = - getApplicationInfoIfNotUpdatedSystemApp(packageManager, userId, packageName); - if (ai != null) { - apps.add(ai); - } - } - return apps; - } - private static Map<String, List<ApplicationInfo>> getDefaultCarrierAssociatedAppsHelper( IPackageManager packageManager, int userId, @@ -400,11 +383,11 @@ public final class CarrierAppUtils { systemCarrierAssociatedAppsDisabledUntilUsed.valueAt(i); for (int j = 0; j < associatedAppPackages.size(); j++) { ApplicationInfo ai = - getApplicationInfoIfNotUpdatedSystemApp( + getApplicationInfoIfSystemApp( packageManager, userId, associatedAppPackages.get(j)); // Only update enabled state for the app on /system. Once it has been updated we // shouldn't touch it. - if (ai != null) { + if (ai != null && !isUpdatedSystemApp(ai)) { List<ApplicationInfo> appList = associatedApps.get(carrierAppPackage); if (appList == null) { appList = new ArrayList<>(); @@ -418,26 +401,6 @@ public final class CarrierAppUtils { } @Nullable - private static ApplicationInfo getApplicationInfoIfNotUpdatedSystemApp( - IPackageManager packageManager, - int userId, - String packageName) { - try { - ApplicationInfo ai = packageManager.getApplicationInfo(packageName, - PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS - | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS - | PackageManager.MATCH_SYSTEM_ONLY - | PackageManager.MATCH_FACTORY_ONLY, userId); - if (ai != null) { - return ai; - } - } catch (RemoteException e) { - Log.w(TAG, "Could not reach PackageManager", e); - } - return null; - } - - @Nullable private static ApplicationInfo getApplicationInfoIfSystemApp( IPackageManager packageManager, int userId, diff --git a/telephony/java/android/service/euicc/EuiccProfileInfo.java b/telephony/java/android/service/euicc/EuiccProfileInfo.java index 8450a9018634..92e419707970 100644 --- a/telephony/java/android/service/euicc/EuiccProfileInfo.java +++ b/telephony/java/android/service/euicc/EuiccProfileInfo.java @@ -29,6 +29,7 @@ import android.text.TextUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -231,7 +232,9 @@ public final class EuiccProfileInfo implements Parcelable { mState = baseProfile.mState; mCarrierIdentifier = baseProfile.mCarrierIdentifier; mPolicyRules = baseProfile.mPolicyRules; - mAccessRules = Arrays.asList(baseProfile.mAccessRules); + mAccessRules = baseProfile.mAccessRules == null + ? Collections.emptyList() + : Arrays.asList(baseProfile.mAccessRules); } /** Builds the profile instance. */ diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index d319e37c4ba6..b75f9a15f66f 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -2495,15 +2495,15 @@ public class CarrierConfigManager { /** * List of 4 customized 5G SS reference signal received quality (SSRSRQ) thresholds. * <p> - * Reference: 3GPP TS 38.215 + * Reference: 3GPP TS 38.215; 3GPP TS 38.133 section 10 * <p> - * 4 threshold integers must be within the boundaries [-20 dB, -3 dB], and the levels are: + * 4 threshold integers must be within the boundaries [-43 dB, 20 dB], and the levels are: * <UL> - * <LI>"NONE: [-20, threshold1]"</LI> + * <LI>"NONE: [-43, threshold1]"</LI> * <LI>"POOR: (threshold1, threshold2]"</LI> * <LI>"MODERATE: (threshold2, threshold3]"</LI> * <LI>"GOOD: (threshold3, threshold4]"</LI> - * <LI>"EXCELLENT: (threshold4, -3]"</LI> + * <LI>"EXCELLENT: (threshold4, 20]"</LI> * </UL> * <p> * This key is considered invalid if the format is violated. If the key is invalid or @@ -4188,12 +4188,12 @@ public class CarrierConfigManager { -65, /* SIGNAL_STRENGTH_GREAT */ }); sDefaults.putIntArray(KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY, - // Boundaries: [-20 dB, -3 dB] + // Boundaries: [-43 dB, 20 dB] new int[] { - -16, /* SIGNAL_STRENGTH_POOR */ - -12, /* SIGNAL_STRENGTH_MODERATE */ - -9, /* SIGNAL_STRENGTH_GOOD */ - -6 /* SIGNAL_STRENGTH_GREAT */ + -31, /* SIGNAL_STRENGTH_POOR */ + -19, /* SIGNAL_STRENGTH_MODERATE */ + -7, /* SIGNAL_STRENGTH_GOOD */ + 6 /* SIGNAL_STRENGTH_GREAT */ }); sDefaults.putIntArray(KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY, // Boundaries: [-23 dB, 40 dB] diff --git a/telephony/java/android/telephony/CellLocation.java b/telephony/java/android/telephony/CellLocation.java index 64776e377fa4..61f68ce32287 100644 --- a/telephony/java/android/telephony/CellLocation.java +++ b/telephony/java/android/telephony/CellLocation.java @@ -16,7 +16,9 @@ package android.telephony; +import android.app.ActivityThread; import android.compat.annotation.UnsupportedAppUsage; +import android.content.Context; import android.os.Bundle; import android.os.RemoteException; import android.os.ServiceManager; @@ -32,15 +34,36 @@ import com.android.internal.telephony.PhoneConstants; public abstract class CellLocation { /** - * Request an update of the current location. If the location has changed, - * a broadcast will be sent to everyone registered with {@link - * PhoneStateListener#LISTEN_CELL_LOCATION}. + * This method will not do anything. + * + * Whenever location changes, a callback will automatically be be sent to + * all registrants of {@link PhoneStateListener#LISTEN_CELL_LOCATION}. + * + * <p>This method is a no-op for callers targeting SDK level 31 or greater. + * <p>This method is a no-op for callers that target SDK level 29 or 30 and lack + * {@link android.Manifest.permission#ACCESS_FINE_LOCATION}. + * <p>This method is a no-op for callers that target SDK level 28 or below and lack + * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}. + * + * Callers wishing to request a single location update should use + * {@link TelephonyManager#requestCellInfoUpdate}. + * + * @deprecated this method has undesirable side-effects, and it calls into the OS without + * access to a {@link android.content.Context Context}, meaning that certain safety checks and + * attribution are error-prone. Given that this method has numerous downsides, and given that + * there are long-available superior alternatives, callers are strongly discouraged from using + * this method. */ + @Deprecated public static void requestLocationUpdate() { + // Since this object doesn't have a context, this is the best we can do. + final Context appContext = ActivityThread.currentApplication(); + if (appContext == null) return; // should never happen + try { ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.getService("phone")); if (phone != null) { - phone.updateServiceLocation(); + phone.updateServiceLocationWithPackageName(appContext.getOpPackageName()); } } catch (RemoteException ex) { // ignore it diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java index 95fe90a47654..8e50bba38e84 100644 --- a/telephony/java/android/telephony/CellSignalStrengthNr.java +++ b/telephony/java/android/telephony/CellSignalStrengthNr.java @@ -54,12 +54,12 @@ public final class CellSignalStrengthNr extends CellSignalStrength implements Pa }; // Lifted from Default carrier configs and max range of SSRSRQ - // Boundaries: [-20 dB, -3 dB] + // Boundaries: [-43 dB, 20 dB] private int[] mSsRsrqThresholds = new int[] { - -16, /* SIGNAL_STRENGTH_POOR */ - -12, /* SIGNAL_STRENGTH_MODERATE */ - -9, /* SIGNAL_STRENGTH_GOOD */ - -6 /* SIGNAL_STRENGTH_GREAT */ + -31, /* SIGNAL_STRENGTH_POOR */ + -19, /* SIGNAL_STRENGTH_MODERATE */ + -7, /* SIGNAL_STRENGTH_GOOD */ + 6 /* SIGNAL_STRENGTH_GREAT */ }; // Lifted from Default carrier configs and max range of SSSINR @@ -183,8 +183,8 @@ public final class CellSignalStrengthNr extends CellSignalStrength implements Pa } /** - * Reference: 3GPP TS 38.215. - * Range: -20 dB to -3 dB. + * Reference: 3GPP TS 38.215; 3GPP TS 38.133 section 10 + * Range: -43 dB to 20 dB. * @return SS reference signal received quality, {@link CellInfo#UNAVAILABLE} means unreported * value. */ diff --git a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java index c667165e7a0e..e91d6fc9d801 100644 --- a/telephony/java/android/telephony/DataSpecificRegistrationInfo.java +++ b/telephony/java/android/telephony/DataSpecificRegistrationInfo.java @@ -72,28 +72,20 @@ public final class DataSpecificRegistrationInfo implements Parcelable { /** * Provides network support info for LTE VoPS and LTE Emergency bearer support */ + @Nullable private final LteVopsSupportInfo mLteVopsSupportInfo; /** - * Indicates if it's using carrier aggregation - * - * @hide - */ - public boolean mIsUsingCarrierAggregation; - - /** * @hide */ DataSpecificRegistrationInfo( int maxDataCalls, boolean isDcNrRestricted, boolean isNrAvailable, - boolean isEnDcAvailable, LteVopsSupportInfo lteVops, - boolean isUsingCarrierAggregation) { + boolean isEnDcAvailable, @Nullable LteVopsSupportInfo lteVops) { this.maxDataCalls = maxDataCalls; this.isDcNrRestricted = isDcNrRestricted; this.isNrAvailable = isNrAvailable; this.isEnDcAvailable = isEnDcAvailable; this.mLteVopsSupportInfo = lteVops; - this.mIsUsingCarrierAggregation = isUsingCarrierAggregation; } /** @@ -102,32 +94,29 @@ public final class DataSpecificRegistrationInfo implements Parcelable { * @param dsri another data specific registration info * @hide */ - DataSpecificRegistrationInfo(DataSpecificRegistrationInfo dsri) { + DataSpecificRegistrationInfo(@NonNull DataSpecificRegistrationInfo dsri) { maxDataCalls = dsri.maxDataCalls; isDcNrRestricted = dsri.isDcNrRestricted; isNrAvailable = dsri.isNrAvailable; isEnDcAvailable = dsri.isEnDcAvailable; mLteVopsSupportInfo = dsri.mLteVopsSupportInfo; - mIsUsingCarrierAggregation = dsri.mIsUsingCarrierAggregation; } - private DataSpecificRegistrationInfo(Parcel source) { + private DataSpecificRegistrationInfo(/* @NonNull */ Parcel source) { maxDataCalls = source.readInt(); isDcNrRestricted = source.readBoolean(); isNrAvailable = source.readBoolean(); isEnDcAvailable = source.readBoolean(); mLteVopsSupportInfo = LteVopsSupportInfo.CREATOR.createFromParcel(source); - mIsUsingCarrierAggregation = source.readBoolean(); } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(/* @NonNull */ Parcel dest, int flags) { dest.writeInt(maxDataCalls); dest.writeBoolean(isDcNrRestricted); dest.writeBoolean(isNrAvailable); dest.writeBoolean(isEnDcAvailable); mLteVopsSupportInfo.writeToParcel(dest, flags); - dest.writeBoolean(mIsUsingCarrierAggregation); } @Override @@ -144,8 +133,7 @@ public final class DataSpecificRegistrationInfo implements Parcelable { .append(" isDcNrRestricted = " + isDcNrRestricted) .append(" isNrAvailable = " + isNrAvailable) .append(" isEnDcAvailable = " + isEnDcAvailable) - .append(" " + mLteVopsSupportInfo.toString()) - .append(" mIsUsingCarrierAggregation = " + mIsUsingCarrierAggregation) + .append(" " + mLteVopsSupportInfo) .append(" }") .toString(); } @@ -153,7 +141,7 @@ public final class DataSpecificRegistrationInfo implements Parcelable { @Override public int hashCode() { return Objects.hash(maxDataCalls, isDcNrRestricted, isNrAvailable, isEnDcAvailable, - mLteVopsSupportInfo, mIsUsingCarrierAggregation); + mLteVopsSupportInfo); } @Override @@ -167,8 +155,7 @@ public final class DataSpecificRegistrationInfo implements Parcelable { && this.isDcNrRestricted == other.isDcNrRestricted && this.isNrAvailable == other.isNrAvailable && this.isEnDcAvailable == other.isEnDcAvailable - && this.mLteVopsSupportInfo.equals(other.mLteVopsSupportInfo) - && this.mIsUsingCarrierAggregation == other.mIsUsingCarrierAggregation; + && Objects.equals(mLteVopsSupportInfo, other.mLteVopsSupportInfo); } public static final @NonNull Parcelable.Creator<DataSpecificRegistrationInfo> CREATOR = @@ -192,23 +179,4 @@ public final class DataSpecificRegistrationInfo implements Parcelable { return mLteVopsSupportInfo; } - /** - * Set the flag indicating if using carrier aggregation. - * - * @param isUsingCarrierAggregation {@code true} if using carrier aggregation. - * @hide - */ - public void setIsUsingCarrierAggregation(boolean isUsingCarrierAggregation) { - mIsUsingCarrierAggregation = isUsingCarrierAggregation; - } - - /** - * Get whether network has configured carrier aggregation or not. - * - * @return {@code true} if using carrier aggregation. - * @hide - */ - public boolean isUsingCarrierAggregation() { - return mIsUsingCarrierAggregation; - } } diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java index 31a83c9334d5..68753e11d395 100644 --- a/telephony/java/android/telephony/NetworkRegistrationInfo.java +++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java @@ -218,6 +218,9 @@ public final class NetworkRegistrationInfo implements Parcelable { @NonNull private String mRplmn; + // Updated based on the accessNetworkTechnology + private boolean mIsUsingCarrierAggregation; + /** * @param domain Network domain. Must be a {@link Domain}. For transport type * {@link AccessNetworkConstants#TRANSPORT_TYPE_WLAN}, this must set to {@link #DOMAIN_PS}. @@ -251,7 +254,7 @@ public final class NetworkRegistrationInfo implements Parcelable { mRegistrationState = registrationState; mRoamingType = (registrationState == REGISTRATION_STATE_ROAMING) ? ServiceState.ROAMING_TYPE_UNKNOWN : ServiceState.ROAMING_TYPE_NOT_ROAMING; - mAccessNetworkTechnology = accessNetworkTechnology; + setAccessNetworkTechnology(accessNetworkTechnology); mRejectCause = rejectCause; mAvailableServices = (availableServices != null) ? new ArrayList<>(availableServices) : new ArrayList<>(); @@ -290,13 +293,11 @@ public final class NetworkRegistrationInfo implements Parcelable { @Nullable CellIdentity cellIdentity, @Nullable String rplmn, int maxDataCalls, boolean isDcNrRestricted, boolean isNrAvailable, boolean isEndcAvailable, - LteVopsSupportInfo lteVopsSupportInfo, - boolean isUsingCarrierAggregation) { + LteVopsSupportInfo lteVopsSupportInfo) { this(domain, transportType, registrationState, accessNetworkTechnology, rejectCause, emergencyOnly, availableServices, cellIdentity, rplmn); mDataSpecificInfo = new DataSpecificRegistrationInfo( - maxDataCalls, isDcNrRestricted, isNrAvailable, isEndcAvailable, lteVopsSupportInfo, - isUsingCarrierAggregation); + maxDataCalls, isDcNrRestricted, isNrAvailable, isEndcAvailable, lteVopsSupportInfo); updateNrState(); } @@ -317,6 +318,7 @@ public final class NetworkRegistrationInfo implements Parcelable { DataSpecificRegistrationInfo.class.getClassLoader()); mNrState = source.readInt(); mRplmn = source.readString(); + mIsUsingCarrierAggregation = source.readBoolean(); } /** @@ -331,6 +333,7 @@ public final class NetworkRegistrationInfo implements Parcelable { mRegistrationState = nri.mRegistrationState; mRoamingType = nri.mRoamingType; mAccessNetworkTechnology = nri.mAccessNetworkTechnology; + mIsUsingCarrierAggregation = nri.mIsUsingCarrierAggregation; mRejectCause = nri.mRejectCause; mEmergencyOnly = nri.mEmergencyOnly; mAvailableServices = new ArrayList<>(nri.mAvailableServices); @@ -484,9 +487,7 @@ public final class NetworkRegistrationInfo implements Parcelable { if (tech == TelephonyManager.NETWORK_TYPE_LTE_CA) { // For old device backward compatibility support tech = TelephonyManager.NETWORK_TYPE_LTE; - if (mDataSpecificInfo != null) { - mDataSpecificInfo.setIsUsingCarrierAggregation(true); - } + mIsUsingCarrierAggregation = true; } mAccessNetworkTechnology = tech; } @@ -511,6 +512,27 @@ public final class NetworkRegistrationInfo implements Parcelable { } /** + * Set whether network has configured carrier aggregation or not. + * + * @param isUsingCarrierAggregation set whether or not carrier aggregation is used. + * + * @hide + */ + public void setIsUsingCarrierAggregation(boolean isUsingCarrierAggregation) { + mIsUsingCarrierAggregation = isUsingCarrierAggregation; + } + + /** + * Get whether network has configured carrier aggregation or not. + * + * @return {@code true} if using carrier aggregation. + * @hide + */ + public boolean isUsingCarrierAggregation() { + return mIsUsingCarrierAggregation; + } + + /** * @hide */ @Nullable @@ -616,6 +638,7 @@ public final class NetworkRegistrationInfo implements Parcelable { .append(" dataSpecificInfo=").append(mDataSpecificInfo) .append(" nrState=").append(nrStateToString(mNrState)) .append(" rRplmn=").append(mRplmn) + .append(" isUsingCarrierAggregation=").append(mIsUsingCarrierAggregation) .append("}").toString(); } @@ -623,7 +646,8 @@ public final class NetworkRegistrationInfo implements Parcelable { public int hashCode() { return Objects.hash(mDomain, mTransportType, mRegistrationState, mRoamingType, mAccessNetworkTechnology, mRejectCause, mEmergencyOnly, mAvailableServices, - mCellIdentity, mVoiceSpecificInfo, mDataSpecificInfo, mNrState, mRplmn); + mCellIdentity, mVoiceSpecificInfo, mDataSpecificInfo, mNrState, mRplmn, + mIsUsingCarrierAggregation); } @Override @@ -643,6 +667,7 @@ public final class NetworkRegistrationInfo implements Parcelable { && mRejectCause == other.mRejectCause && mEmergencyOnly == other.mEmergencyOnly && mAvailableServices.equals(other.mAvailableServices) + && mIsUsingCarrierAggregation == other.mIsUsingCarrierAggregation && Objects.equals(mCellIdentity, other.mCellIdentity) && Objects.equals(mVoiceSpecificInfo, other.mVoiceSpecificInfo) && Objects.equals(mDataSpecificInfo, other.mDataSpecificInfo) @@ -669,6 +694,7 @@ public final class NetworkRegistrationInfo implements Parcelable { dest.writeParcelable(mDataSpecificInfo, 0); dest.writeInt(mNrState); dest.writeString(mRplmn); + dest.writeBoolean(mIsUsingCarrierAggregation); } /** diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java index a9abe89abab0..dd37ec3b6e54 100644 --- a/telephony/java/android/telephony/PreciseDataConnectionState.java +++ b/telephony/java/android/telephony/PreciseDataConnectionState.java @@ -266,7 +266,6 @@ public final class PreciseDataConnectionState implements Parcelable { * * @return the ApnSetting that was used to configure this data connection. */ - // FIXME: This shouldn't be nullable; update once the ApnSetting is supplied correctly public @Nullable ApnSetting getApnSetting() { return mApnSetting; } diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 5c84297d3b86..243b24b667c7 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -1392,29 +1392,14 @@ public class ServiceState implements Parcelable { /** @hide */ public boolean isUsingCarrierAggregation() { - boolean isUsingCa = false; - NetworkRegistrationInfo nri = getNetworkRegistrationInfo( - NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); - if (nri != null) { - DataSpecificRegistrationInfo dsri = nri.getDataSpecificInfo(); - if (dsri != null) { - isUsingCa = dsri.isUsingCarrierAggregation(); - } - } - return isUsingCa || getCellBandwidths().length > 1; - } + if (getCellBandwidths().length > 1) return true; - /** @hide */ - public void setIsUsingCarrierAggregation(boolean ca) { - NetworkRegistrationInfo nri = getNetworkRegistrationInfo( - NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN); - if (nri != null) { - DataSpecificRegistrationInfo dsri = nri.getDataSpecificInfo(); - if (dsri != null) { - dsri.setIsUsingCarrierAggregation(ca); - addNetworkRegistrationInfo(nri); + synchronized (mNetworkRegistrationInfos) { + for (NetworkRegistrationInfo nri : mNetworkRegistrationInfos) { + if (nri.isUsingCarrierAggregation()) return true; } } + return false; } /** diff --git a/telephony/java/android/telephony/SmsCbEtwsInfo.java b/telephony/java/android/telephony/SmsCbEtwsInfo.java index 2a7f7ad81e3b..a98916d715e4 100644 --- a/telephony/java/android/telephony/SmsCbEtwsInfo.java +++ b/telephony/java/android/telephony/SmsCbEtwsInfo.java @@ -27,6 +27,7 @@ import com.android.internal.telephony.uicc.IccUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.time.DateTimeException; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.Arrays; @@ -173,7 +174,7 @@ public final class SmsCbEtwsInfo implements Parcelable { /** * Returns the Warning-Security-Information timestamp (GSM primary notifications only). * As of Release 10, 3GPP TS 23.041 states that the UE shall ignore this value if received. - * @return a UTC timestamp in System.currentTimeMillis() format, or 0 if not present + * @return a UTC timestamp in System.currentTimeMillis() format, or 0 if not present or invalid. */ public long getPrimaryNotificationTimestamp() { if (mWarningSecurityInformation == null || mWarningSecurityInformation.length < 7) { @@ -201,18 +202,23 @@ public final class SmsCbEtwsInfo implements Parcelable { // timezoneOffset is in quarter hours. int timeZoneOffsetSeconds = timezoneOffset * 15 * 60; - LocalDateTime localDateTime = LocalDateTime.of( - // We only need to support years above 2000. - year + 2000, - month /* 1-12 */, - day, - hour, - minute, - second); - - long epochSeconds = localDateTime.toEpochSecond(ZoneOffset.UTC) - timeZoneOffsetSeconds; - // Convert to milliseconds, ignore overflow. - return epochSeconds * 1000; + try { + LocalDateTime localDateTime = LocalDateTime.of( + // We only need to support years above 2000. + year + 2000, + month /* 1-12 */, + day, + hour, + minute, + second); + + long epochSeconds = localDateTime.toEpochSecond(ZoneOffset.UTC) - timeZoneOffsetSeconds; + // Convert to milliseconds, ignore overflow. + return epochSeconds * 1000; + } catch (DateTimeException ex) { + // No-op + } + return 0; } /** diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index 7456aabc8e19..abcc82bcdfe7 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -615,10 +615,12 @@ public final class SmsManager { } if (priority < 0x00 || priority > 0x03) { + Log.e(TAG, "Invalid Priority " + priority); priority = SMS_MESSAGE_PRIORITY_NOT_SPECIFIED; } if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) { + Log.e(TAG, "Invalid Validity Period " + validityPeriod); validityPeriod = SMS_MESSAGE_PERIOD_NOT_SPECIFIED; } @@ -1174,10 +1176,12 @@ public final class SmsManager { } if (priority < 0x00 || priority > 0x03) { + Log.e(TAG, "Invalid Priority " + priority); priority = SMS_MESSAGE_PRIORITY_NOT_SPECIFIED; } if (validityPeriod < 0x05 || validityPeriod > 0x09b0a0) { + Log.e(TAG, "Invalid Validity Period " + validityPeriod); validityPeriod = SMS_MESSAGE_PERIOD_NOT_SPECIFIED; } diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java index 7266fc05cffa..392670ba992c 100644 --- a/telephony/java/android/telephony/SmsMessage.java +++ b/telephony/java/android/telephony/SmsMessage.java @@ -255,28 +255,6 @@ public class SmsMessage { } /** - * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the - * +CMT unsolicited response (PDU mode, of course) - * +CMT: [<alpha>],<length><CR><LF><pdu> - * - * Only public for debugging and for RIL - * - * {@hide} - */ - public static SmsMessage newFromCMT(byte[] pdu) { - // received SMS in 3GPP format - SmsMessageBase wrappedMessage = - com.android.internal.telephony.gsm.SmsMessage.newFromCMT(pdu); - - if (wrappedMessage != null) { - return new SmsMessage(wrappedMessage); - } else { - Rlog.e(LOG_TAG, "newFromCMT(): wrappedMessage is null"); - return null; - } - } - - /** * Creates an SmsMessage from an SMS EF record. * * @param index Index of SMS EF record. diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 68977068aa74..4c7253ce76c6 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -110,8 +110,6 @@ import com.android.internal.telephony.RILConstants; import com.android.internal.telephony.SmsApplication; import com.android.telephony.Rlog; -import java.io.FileInputStream; -import java.io.IOException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; @@ -124,8 +122,6 @@ import java.util.Objects; import java.util.UUID; import java.util.concurrent.Executor; import java.util.function.Consumer; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * Provides access to information about the telephony services on @@ -2189,58 +2185,6 @@ public class TelephonyManager { } /** - * Enables location update notifications. {@link PhoneStateListener#onCellLocationChanged - * PhoneStateListener.onCellLocationChanged} will be called on location updates. - * - * @hide - */ - @RequiresPermission(android.Manifest.permission.CONTROL_LOCATION_UPDATES) - public void enableLocationUpdates() { - enableLocationUpdates(getSubId()); - } - - /** - * Enables location update notifications for a subscription. - * {@link PhoneStateListener#onCellLocationChanged - * PhoneStateListener.onCellLocationChanged} will be called on location updates. - * - * @param subId for which the location updates are enabled - * @hide - */ - @RequiresPermission(android.Manifest.permission.CONTROL_LOCATION_UPDATES) - public void enableLocationUpdates(int subId) { - try { - ITelephony telephony = getITelephony(); - if (telephony != null) - telephony.enableLocationUpdatesForSubscriber(subId); - } catch (RemoteException ex) { - } catch (NullPointerException ex) { - } - } - - /** - * Disables location update notifications. {@link PhoneStateListener#onCellLocationChanged - * PhoneStateListener.onCellLocationChanged} will be called on location updates. - * - * @hide - */ - @RequiresPermission(android.Manifest.permission.CONTROL_LOCATION_UPDATES) - public void disableLocationUpdates() { - disableLocationUpdates(getSubId()); - } - - /** @hide */ - public void disableLocationUpdates(int subId) { - try { - ITelephony telephony = getITelephony(); - if (telephony != null) - telephony.disableLocationUpdatesForSubscriber(subId); - } catch (RemoteException ex) { - } catch (NullPointerException ex) { - } - } - - /** * Returns the neighboring cell information of the device. * * @return List of NeighboringCellInfo or null if info unavailable. @@ -2440,7 +2384,8 @@ public class TelephonyManager { return PhoneConstants.PHONE_TYPE_CDMA; case RILConstants.NETWORK_MODE_LTE_ONLY: - if (getLteOnCdmaModeStatic() == PhoneConstants.LTE_ON_CDMA_TRUE) { + if (TelephonyProperties.lte_on_cdma_device().orElse( + PhoneConstants.LTE_ON_CDMA_FALSE) == PhoneConstants.LTE_ON_CDMA_TRUE) { return PhoneConstants.PHONE_TYPE_CDMA; } else { return PhoneConstants.PHONE_TYPE_GSM; @@ -2451,35 +2396,6 @@ public class TelephonyManager { } /** - * The contents of the /proc/cmdline file - */ - @UnsupportedAppUsage - private static String getProcCmdLine() - { - String cmdline = ""; - FileInputStream is = null; - try { - is = new FileInputStream("/proc/cmdline"); - byte [] buffer = new byte[2048]; - int count = is.read(buffer); - if (count > 0) { - cmdline = new String(buffer, 0, count); - } - } catch (IOException e) { - Rlog.d(TAG, "No /proc/cmdline exception=" + e); - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException e) { - } - } - } - Rlog.d(TAG, "/proc/cmdline=" + cmdline); - return cmdline; - } - - /** * @return The max value for the timeout passed in {@link #requestNumberVerification}. * @hide */ @@ -2488,56 +2404,6 @@ public class TelephonyManager { return MAX_NUMBER_VERIFICATION_TIMEOUT_MILLIS; } - /** Kernel command line */ - private static final String sKernelCmdLine = getProcCmdLine(); - - /** Pattern for selecting the product type from the kernel command line */ - private static final Pattern sProductTypePattern = - Pattern.compile("\\sproduct_type\\s*=\\s*(\\w+)"); - - /** The ProductType used for LTE on CDMA devices */ - private static final String sLteOnCdmaProductType = - TelephonyProperties.lte_on_cdma_product_type().orElse(""); - - /** - * Return if the current radio is LTE on CDMA. This - * is a tri-state return value as for a period of time - * the mode may be unknown. - * - * @return {@link PhoneConstants#LTE_ON_CDMA_UNKNOWN}, {@link PhoneConstants#LTE_ON_CDMA_FALSE} - * or {@link PhoneConstants#LTE_ON_CDMA_TRUE} - * - * @hide - */ - @UnsupportedAppUsage - public static int getLteOnCdmaModeStatic() { - int retVal; - int curVal; - String productType = ""; - - curVal = TelephonyProperties.lte_on_cdma_device().orElse( - PhoneConstants.LTE_ON_CDMA_UNKNOWN); - retVal = curVal; - if (retVal == PhoneConstants.LTE_ON_CDMA_UNKNOWN) { - Matcher matcher = sProductTypePattern.matcher(sKernelCmdLine); - if (matcher.find()) { - productType = matcher.group(1); - if (sLteOnCdmaProductType.equals(productType)) { - retVal = PhoneConstants.LTE_ON_CDMA_TRUE; - } else { - retVal = PhoneConstants.LTE_ON_CDMA_FALSE; - } - } else { - retVal = PhoneConstants.LTE_ON_CDMA_FALSE; - } - } - - Rlog.d(TAG, "getLteOnCdmaMode=" + retVal + " curVal=" + curVal + - " product_type='" + productType + - "' lteOnCdmaProductType='" + sLteOnCdmaProductType + "'"); - return retVal; - } - // // // Current Network @@ -8109,6 +7975,140 @@ public class TelephonyManager { return false; } + /** @hide */ + @IntDef({ + ALLOWED_NETWORK_TYPES_REASON_POWER + }) + @Retention(RetentionPolicy.SOURCE) + public @interface AllowedNetworkTypesReason{} + + /** + * To indicate allowed network type change is requested by power manager. + * Power Manger configuration won't affect the settings configured through + * {@link setAllowedNetworkTypes} and will result in allowing network types that are in both + * configurations (i.e intersection of both sets). + * @hide + */ + public static final int ALLOWED_NETWORK_TYPES_REASON_POWER = 0; + + /** + * Set the allowed network types of the device and + * provide the reason triggering the allowed network change. + * This can be called for following reasons + * <ol> + * <li>Allowed network types control by power manager + * {@link #ALLOWED_NETWORK_TYPES_REASON_POWER} + * </ol> + * This API will result in allowing an intersection of allowed network types for all reasons, + * including the configuration done through {@link setAllowedNetworkTypes}. + * While this API and {@link setAllowedNetworkTypes} is controlling allowed network types + * on device, user preference will still be set through {@link #setPreferredNetworkTypeBitmask}. + * Thus resultant network type configured on modem will be an intersection of the network types + * from setAllowedNetworkTypesForReason, {@link setAllowedNetworkTypes} + * and {@link #setPreferredNetworkTypeBitmask}. + * + * @param reason the reason the allowed network type change is taking place + * @param allowedNetworkTypes The bitmask of allowed network types. + * @throws IllegalStateException if the Telephony process is not currently available. + * @throws IllegalArgumentException if invalid AllowedNetworkTypesReason is passed. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) + public void setAllowedNetworkTypesForReason(@AllowedNetworkTypesReason int reason, + @NetworkTypeBitMask long allowedNetworkTypes) { + if (reason != ALLOWED_NETWORK_TYPES_REASON_POWER) { + throw new IllegalArgumentException("invalid AllowedNetworkTypesReason."); + } + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + telephony.setAllowedNetworkTypesForReason(getSubId(), reason, + allowedNetworkTypes); + } else { + throw new IllegalStateException("telephony service is null."); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "setAllowedNetworkTypesForReason RemoteException", ex); + ex.rethrowFromSystemServer(); + } + } + + /** + * Get the allowed network types for certain reason. + * + * {@link #getAllowedNetworkTypesForReason} returns allowed network type for a + * specific reason. For effective allowed network types configured on device, + * query {@link getEffectiveAllowedNetworkTypes} + * + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + *s + * @param reason the reason the allowed network type change is taking place + * @return the allowed network type bitmask + * @throws IllegalStateException if the Telephony process is not currently available. + * @throws IllegalArgumentException if invalid AllowedNetworkTypesReason is passed. + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public @NetworkTypeBitMask long getAllowedNetworkTypesForReason( + @AllowedNetworkTypesReason int reason) { + if (reason != ALLOWED_NETWORK_TYPES_REASON_POWER) { + throw new IllegalArgumentException("invalid AllowedNetworkTypesReason."); + } + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getAllowedNetworkTypesForReason(getSubId(), reason); + } else { + throw new IllegalStateException("telephony service is null."); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "getAllowedNetworkTypesForReason RemoteException", ex); + ex.rethrowFromSystemServer(); + } + return -1; + } + + /** + * Get bit mask of all network types. + * + * @return bit mask of all network types + * @hide + */ + public static @NetworkTypeBitMask long getAllNetworkTypesBitmask() { + return NETWORK_STANDARDS_FAMILY_BITMASK_3GPP | NETWORK_STANDARDS_FAMILY_BITMASK_3GPP2; + } + + /** + * Get the allowed network types configured on the device. + * This API will return an intersection of allowed network types for all reasons, + * including the configuration done through setAllowedNetworkTypes + * + * <p>Requires Permission: + * {@link android.Manifest.permission#READ_PRIVILEGED_PHONE_STATE READ_PRIVILEGED_PHONE_STATE} + * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). + * + * @return the allowed network type bitmask + * @throws IllegalStateException if the Telephony process is not currently available. + * @hide + */ + @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) + public @NetworkTypeBitMask long getEffectiveAllowedNetworkTypes() { + try { + ITelephony telephony = getITelephony(); + if (telephony != null) { + return telephony.getEffectiveAllowedNetworkTypes(getSubId()); + } else { + throw new IllegalStateException("telephony service is null."); + } + } catch (RemoteException ex) { + Rlog.e(TAG, "getEffectiveAllowedNetworkTypes RemoteException", ex); + ex.rethrowFromSystemServer(); + } + return -1; + } + /** * Set the preferred network type to global mode which includes LTE, CDMA, EvDo and GSM/WCDMA. * @@ -8969,17 +8969,14 @@ public class TelephonyManager { return RADIO_POWER_UNAVAILABLE; } - /** @hide */ + /** + * This method should not be used due to privacy and stability concerns. + * + * @hide + */ @SystemApi - @SuppressLint("Doclava125") public void updateServiceLocation() { - try { - ITelephony telephony = getITelephony(); - if (telephony != null) - telephony.updateServiceLocation(); - } catch (RemoteException e) { - Log.e(TAG, "Error calling ITelephony#updateServiceLocation", e); - } + Log.e(TAG, "Do not call TelephonyManager#updateServiceLocation()"); } /** @hide */ diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 5a6b997f0723..bf81ddcb0c15 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -222,42 +222,29 @@ interface ITelephony { boolean setRadioPower(boolean turnOn); /** - * Request to update location information in service state + * This method has been removed due to security and stability issues. */ @UnsupportedAppUsage void updateServiceLocation(); /** - * Request to update location information for a subscrition in service state - * @param subId user preferred subId. + * Version of updateServiceLocation that records the caller and validates permissions. */ - void updateServiceLocationForSubscriber(int subId); + void updateServiceLocationWithPackageName(String callingPkg); /** - * Enable location update notifications. + * This method has been removed due to security and stability issues. */ @UnsupportedAppUsage void enableLocationUpdates(); /** - * Enable location update notifications. - * @param subId user preferred subId. - */ - void enableLocationUpdatesForSubscriber(int subId); - - /** - * Disable location update notifications. + * This method has been removed due to security and stability issues. */ @UnsupportedAppUsage void disableLocationUpdates(); /** - * Disable location update notifications. - * @param subId user preferred subId. - */ - void disableLocationUpdatesForSubscriber(int subId); - - /** * Allow mobile data connections. */ @UnsupportedAppUsage @@ -956,6 +943,35 @@ interface ITelephony { boolean setAllowedNetworkTypes(int subId, long allowedNetworkTypes); /** + * Get the allowed network types for certain reason. + * + * @param subId the id of the subscription. + * @param reason the reason the allowed network type change is taking place + * @return allowedNetworkTypes the allowed network types. + */ + long getAllowedNetworkTypesForReason(int subId, int reason); + + /** + * Get the effective allowed network types on the device. This API will + * return an intersection of allowed network types for all reasons, + * including the configuration done through setAllowedNetworkTypes + * + * @param subId the id of the subscription. + * @return allowedNetworkTypes the allowed network types. + */ + long getEffectiveAllowedNetworkTypes(int subId); + + /** + * Set the allowed network types and provide the reason triggering the allowed network change. + * + * @param subId the id of the subscription. + * @param reason the reason the allowed network type change is taking place + * @param allowedNetworkTypes the allowed network types. + * @return true on success; false on any failure. + */ + boolean setAllowedNetworkTypesForReason(int subId, int reason, long allowedNetworkTypes); + + /** * Set the preferred network type. * Used for device configuration by some CDMA operators. * diff --git a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java index 3bd8cdd23df3..f8ab87d042eb 100644 --- a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java +++ b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java @@ -65,13 +65,7 @@ public class Sms7BitEncodingTranslator { return ""; } - if (!mIs7BitTranslationTableLoaded) { - mTranslationTableCommon = new SparseIntArray(); - mTranslationTableGSM = new SparseIntArray(); - mTranslationTableCDMA = new SparseIntArray(); - load7BitTranslationTableFromXml(); - mIs7BitTranslationTableLoaded = true; - } + ensure7BitTranslationTableLoaded(); if ((mTranslationTableCommon != null && mTranslationTableCommon.size() > 0) || (mTranslationTableGSM != null && mTranslationTableGSM.size() > 0) || @@ -115,6 +109,8 @@ public class Sms7BitEncodingTranslator { */ int translation = -1; + ensure7BitTranslationTableLoaded(); + if (mTranslationTableCommon != null) { translation = mTranslationTableCommon.get(c, -1); } @@ -155,6 +151,18 @@ public class Sms7BitEncodingTranslator { } } + private static void ensure7BitTranslationTableLoaded() { + synchronized (Sms7BitEncodingTranslator.class) { + if (!mIs7BitTranslationTableLoaded) { + mTranslationTableCommon = new SparseIntArray(); + mTranslationTableGSM = new SparseIntArray(); + mTranslationTableCDMA = new SparseIntArray(); + load7BitTranslationTableFromXml(); + mIs7BitTranslationTableLoaded = true; + } + } + } + /** * Load the whole translation table file from the framework resource * encoded in XML. diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java index 3f85de358ea2..109dd3b30827 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java @@ -35,6 +35,7 @@ import com.android.internal.util.BitwiseOutputStream; import com.android.telephony.Rlog; import java.io.ByteArrayOutputStream; +import java.time.DateTimeException; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; @@ -315,10 +316,16 @@ public final class BearerData { } public long toMillis() { - LocalDateTime localDateTime = - LocalDateTime.of(year, monthOrdinal, monthDay, hour, minute, second); - Instant instant = localDateTime.toInstant(mZoneId.getRules().getOffset(localDateTime)); - return instant.toEpochMilli(); + try { + LocalDateTime localDateTime = + LocalDateTime.of(year, monthOrdinal, monthDay, hour, minute, second); + Instant instant = + localDateTime.toInstant(mZoneId.getRules().getOffset(localDateTime)); + return instant.toEpochMilli(); + } catch (DateTimeException ex) { + Rlog.e(LOG_TAG, "Invalid timestamp", ex); + } + return 0; } @@ -1093,7 +1100,7 @@ public final class BearerData { bData.hasUserDataHeader = (inStream.read(1) == 1); inStream.skip(3); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "MESSAGE_IDENTIFIER decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1462,7 +1469,7 @@ public final class BearerData { bData.reportReq = (inStream.read(1) == 1); inStream.skip(4); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "REPLY_OPTION decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1481,7 +1488,7 @@ public final class BearerData { decodeSuccess = true; bData.numberOfMessages = IccUtils.cdmaBcdByteToInt((byte)inStream.read(8)); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "NUMBER_OF_MESSAGES decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1500,7 +1507,7 @@ public final class BearerData { decodeSuccess = true; bData.depositIndex = (inStream.read(8) << 8) | inStream.read(8); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "MESSAGE_DEPOSIT_INDEX decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1587,7 +1594,7 @@ public final class BearerData { bData.errorClass = inStream.read(2); bData.messageStatus = inStream.read(6); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "MESSAGE_STATUS decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1607,7 +1614,7 @@ public final class BearerData { decodeSuccess = true; bData.msgCenterTimeStamp = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8)); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "MESSAGE_CENTER_TIME_STAMP decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1626,7 +1633,7 @@ public final class BearerData { decodeSuccess = true; bData.validityPeriodAbsolute = TimeStamp.fromByteArray(inStream.readByteArray(6 * 8)); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "VALIDITY_PERIOD_ABSOLUTE decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1646,7 +1653,7 @@ public final class BearerData { bData.deferredDeliveryTimeAbsolute = TimeStamp.fromByteArray( inStream.readByteArray(6 * 8)); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_ABSOLUTE decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1665,7 +1672,7 @@ public final class BearerData { decodeSuccess = true; bData.deferredDeliveryTimeRelative = inStream.read(8); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "VALIDITY_PERIOD_RELATIVE decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1685,7 +1692,7 @@ public final class BearerData { decodeSuccess = true; bData.validityPeriodRelative = inStream.read(8); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "DEFERRED_DELIVERY_TIME_RELATIVE decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1706,7 +1713,7 @@ public final class BearerData { bData.privacy = inStream.read(2); inStream.skip(6); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "PRIVACY_INDICATOR decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1726,7 +1733,7 @@ public final class BearerData { decodeSuccess = true; bData.language = inStream.read(8); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "LANGUAGE_INDICATOR decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1747,7 +1754,7 @@ public final class BearerData { bData.displayMode = inStream.read(2); inStream.skip(6); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "DISPLAY_MODE decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1768,7 +1775,7 @@ public final class BearerData { bData.priority = inStream.read(2); inStream.skip(6); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "PRIORITY_INDICATOR decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1789,7 +1796,7 @@ public final class BearerData { bData.alert = inStream.read(2); inStream.skip(6); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "ALERT_ON_MESSAGE_DELIVERY decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1809,7 +1816,7 @@ public final class BearerData { decodeSuccess = true; bData.userResponseCode = inStream.read(8); } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "USER_RESPONSE_CODE decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ")"); @@ -1871,7 +1878,7 @@ public final class BearerData { decodeSuccess = true; } - if ((! decodeSuccess) || (paramBits > 0)) { + if ((!decodeSuccess) || (paramBits > 0)) { Rlog.d(LOG_TAG, "SERVICE_CATEGORY_PROGRAM_DATA decode " + (decodeSuccess ? "succeeded" : "failed") + " (extra bits = " + paramBits + ')'); diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java index 122f0851cf37..7d5710e32194 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java @@ -43,6 +43,7 @@ import com.android.telephony.Rlog; import java.io.ByteArrayOutputStream; import java.io.UnsupportedEncodingException; import java.text.ParseException; +import java.time.DateTimeException; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; @@ -91,14 +92,15 @@ public class SmsMessage extends SmsMessageBase { private int mVoiceMailCount = 0; + /** TP-Validity-Period-Format (TP-VPF). See TS 23.040, 9.2.3.3 */ private static final int VALIDITY_PERIOD_FORMAT_NONE = 0x00; private static final int VALIDITY_PERIOD_FORMAT_ENHANCED = 0x01; private static final int VALIDITY_PERIOD_FORMAT_RELATIVE = 0x02; private static final int VALIDITY_PERIOD_FORMAT_ABSOLUTE = 0x03; - //Validity Period min - 5 mins + // Validity Period min - 5 mins private static final int VALIDITY_PERIOD_MIN = 5; - //Validity Period max - 63 weeks + // Validity Period max - 63 weeks private static final int VALIDITY_PERIOD_MAX = 635040; private static final int INVALID_VALIDITY_PERIOD = -1; @@ -140,38 +142,6 @@ public class SmsMessage extends SmsMessageBase { } /** - * TS 27.005 3.4.1 lines[0] and lines[1] are the two lines read from the - * +CMT unsolicited response (PDU mode, of course) - * +CMT: [<alpha>],<length><CR><LF><pdu> - * - * Only public for debugging - * - * {@hide} - */ - public static SmsMessage newFromCMT(byte[] pdu) { - try { - SmsMessage msg = new SmsMessage(); - msg.parsePdu(pdu); - return msg; - } catch (RuntimeException ex) { - Rlog.e(LOG_TAG, "SMS PDU parsing failed: ", ex); - return null; - } - } - - /** @hide */ - public static SmsMessage newFromCDS(byte[] pdu) { - try { - SmsMessage msg = new SmsMessage(); - msg.parsePdu(pdu); - return msg; - } catch (RuntimeException ex) { - Rlog.e(LOG_TAG, "CDS SMS PDU parsing failed: ", ex); - return null; - } - } - - /** * Creates an SmsMessage from an SMS EF record. * * @param index Index of SMS EF record. @@ -225,20 +195,20 @@ public class SmsMessage extends SmsMessageBase { } /** - * Get Encoded Relative Validty Period Value from Validity period in mins. + * Gets Encoded Relative Validity Period Value from Validity period in mins. * * @param validityPeriod Validity period in mins. * * Refer specification 3GPP TS 23.040 V6.8.1 section 9.2.3.12.1. - * ||relValidityPeriod (TP-VP) || || validityPeriod || - * - * 0 to 143 ---> (TP-VP + 1) x 5 minutes - * - * 144 to 167 ---> 12 hours + ((TP-VP -143) x 30 minutes) - * - * 168 to 196 ---> (TP-VP - 166) x 1 day - * - * 197 to 255 ---> (TP-VP - 192) x 1 week + * ------------------------------------------------------------ + * TP-VP | Validity period + * (Relative format) | value + * ------------------------------------------------------------ + * 0 to 143 | (TP-VP + 1) x 5 minutes + * 144 to 167 | 12 hours + ((TP-VP -143) x 30 minutes) + * 168 to 196 | (TP-VP - 166) x 1 day + * 197 to 255 | (TP-VP - 192) x 1 week + * ------------------------------------------------------------ * * @return relValidityPeriod Encoded Relative Validity Period Value. * @hide @@ -246,19 +216,16 @@ public class SmsMessage extends SmsMessageBase { public static int getRelativeValidityPeriod(int validityPeriod) { int relValidityPeriod = INVALID_VALIDITY_PERIOD; - if (validityPeriod < VALIDITY_PERIOD_MIN || validityPeriod > VALIDITY_PERIOD_MAX) { - Rlog.e(LOG_TAG,"Invalid Validity Period" + validityPeriod); - return relValidityPeriod; - } - - if (validityPeriod <= 720) { - relValidityPeriod = (validityPeriod / 5) - 1; - } else if (validityPeriod <= 1440) { - relValidityPeriod = ((validityPeriod - 720) / 30) + 143; - } else if (validityPeriod <= 43200) { - relValidityPeriod = (validityPeriod / 1440) + 166; - } else if (validityPeriod <= 635040) { - relValidityPeriod = (validityPeriod / 10080) + 192; + if (validityPeriod >= VALIDITY_PERIOD_MIN) { + if (validityPeriod <= 720) { + relValidityPeriod = (validityPeriod / 5) - 1; + } else if (validityPeriod <= 1440) { + relValidityPeriod = ((validityPeriod - 720) / 30) + 143; + } else if (validityPeriod <= 43200) { + relValidityPeriod = (validityPeriod / 1440) + 166; + } else if (validityPeriod <= VALIDITY_PERIOD_MAX) { + relValidityPeriod = (validityPeriod / 10080) + 192; + } } return relValidityPeriod; } @@ -368,17 +335,19 @@ public class SmsMessage extends SmsMessageBase { SubmitPdu ret = new SubmitPdu(); - int validityPeriodFormat = VALIDITY_PERIOD_FORMAT_NONE; - int relativeValidityPeriod = INVALID_VALIDITY_PERIOD; + int relativeValidityPeriod = getRelativeValidityPeriod(validityPeriod); - // TP-Validity-Period-Format (TP-VPF) in 3GPP TS 23.040 V6.8.1 section 9.2.3.3 - //bit 4:3 = 10 - TP-VP field present - relative format - if((relativeValidityPeriod = getRelativeValidityPeriod(validityPeriod)) >= 0) { - validityPeriodFormat = VALIDITY_PERIOD_FORMAT_RELATIVE; + byte mtiByte = 0x01; // SMS-SUBMIT + + if (header != null) { + // Set TP-UDHI + mtiByte |= 0x40; } - byte mtiByte = (byte)(0x01 | (validityPeriodFormat << 0x03) | - (header != null ? 0x40 : 0x00)); + if (relativeValidityPeriod != INVALID_VALIDITY_PERIOD) { + // Set TP-Validity-Period-Format (TP-VPF) + mtiByte |= VALIDITY_PERIOD_FORMAT_RELATIVE << 3; + } ByteArrayOutputStream bo = getSubmitPduHead( scAddress, destinationAddress, mtiByte, @@ -450,8 +419,8 @@ public class SmsMessage extends SmsMessageBase { bo.write(0x08); } - if (validityPeriodFormat == VALIDITY_PERIOD_FORMAT_RELATIVE) { - // ( TP-Validity-Period - relative format) + // TP-Validity-Period (TP-VP) + if (relativeValidityPeriod != INVALID_VALIDITY_PERIOD) { bo.write(relativeValidityPeriod); } @@ -888,10 +857,9 @@ public class SmsMessage extends SmsMessageBase { } /** - * Parses an SC timestamp and returns a currentTimeMillis()-style - * timestamp + * Parses an SC timestamp and returns a currentTimeMillis()-style timestamp, or 0 if + * invalid. */ - long getSCTimestampMillis() { // TP-Service-Centre-Time-Stamp int year = IccUtils.gsmBcdByteToInt(mPdu[mCur++]); @@ -917,16 +885,22 @@ public class SmsMessage extends SmsMessageBase { // It's 2006. Should I really support years < 2000? int fullYear = year >= 90 ? year + 1900 : year + 2000; - LocalDateTime localDateTime = LocalDateTime.of( - fullYear, - month /* 1-12 */, - day, - hour, - minute, - second); - long epochSeconds = localDateTime.toEpochSecond(ZoneOffset.UTC) - timeZoneOffsetSeconds; - // Convert to milliseconds. - return epochSeconds * 1000; + try { + LocalDateTime localDateTime = LocalDateTime.of( + fullYear, + month /* 1-12 */, + day, + hour, + minute, + second); + long epochSeconds = + localDateTime.toEpochSecond(ZoneOffset.UTC) - timeZoneOffsetSeconds; + // Convert to milliseconds. + return epochSeconds * 1000; + } catch (DateTimeException ex) { + Rlog.e(LOG_TAG, "Invalid timestamp", ex); + } + return 0; } /** @@ -1277,6 +1251,7 @@ public class SmsMessage extends SmsMessageBase { mRecipientAddress = p.getAddress(); // TP-Service-Centre-Time-Stamp mScTimeMillis = p.getSCTimestampMillis(); + // TP-Discharge-Time p.getSCTimestampMillis(); // TP-Status mStatus = p.getByte(); @@ -1335,6 +1310,7 @@ public class SmsMessage extends SmsMessageBase { + " data coding scheme: " + mDataCodingScheme); } + // TP-Service-Centre-Time-Stamp mScTimeMillis = p.getSCTimestampMillis(); if (VDBG) Rlog.d(LOG_TAG, "SMS SC timestamp: " + mScTimeMillis); @@ -1377,23 +1353,17 @@ public class SmsMessage extends SmsMessageBase { // TP-Validity-Period-Format int validityPeriodLength = 0; - int validityPeriodFormat = ((firstByte>>3) & 0x3); - if (0x0 == validityPeriodFormat) /* 00, TP-VP field not present*/ - { + int validityPeriodFormat = ((firstByte >> 3) & 0x3); + if (validityPeriodFormat == VALIDITY_PERIOD_FORMAT_NONE) { validityPeriodLength = 0; - } - else if (0x2 == validityPeriodFormat) /* 10, TP-VP: relative format*/ - { + } else if (validityPeriodFormat == VALIDITY_PERIOD_FORMAT_RELATIVE) { validityPeriodLength = 1; - } - else /* other case, 11 or 01, TP-VP: absolute or enhanced format*/ - { + } else { // VALIDITY_PERIOD_FORMAT_ENHANCED or VALIDITY_PERIOD_FORMAT_ABSOLUTE validityPeriodLength = 7; } // TP-Validity-Period is not used on phone, so just ignore it for now. - while (validityPeriodLength-- > 0) - { + while (validityPeriodLength-- > 0) { p.getByte(); } diff --git a/tests/net/java/android/net/NetworkTemplateTest.kt b/tests/net/java/android/net/NetworkTemplateTest.kt index 5dd0fda4da28..9ba56e44fe88 100644 --- a/tests/net/java/android/net/NetworkTemplateTest.kt +++ b/tests/net/java/android/net/NetworkTemplateTest.kt @@ -26,6 +26,7 @@ import android.net.NetworkStats.METERED_ALL import android.net.NetworkStats.ROAMING_ALL import android.net.NetworkTemplate.MATCH_MOBILE import android.net.NetworkTemplate.MATCH_WIFI +import android.net.NetworkTemplate.NETWORK_TYPE_5G_NSA import android.net.NetworkTemplate.NETWORK_TYPE_ALL import android.net.NetworkTemplate.buildTemplateMobileWithRatType import android.telephony.TelephonyManager @@ -145,11 +146,13 @@ class NetworkTemplateTest { assertParcelSane(templateWifi, 8) } - // Verify NETWORK_TYPE_ALL does not conflict with TelephonyManager#NETWORK_TYPE_* constants. + // Verify NETWORK_TYPE_* constants in NetworkTemplate do not conflict with + // TelephonyManager#NETWORK_TYPE_* constants. @Test - fun testNetworkTypeAll() { + fun testNetworkTypeConstants() { for (ratType in TelephonyManager.getAllNetworkTypes()) { assertNotEquals(NETWORK_TYPE_ALL, ratType) + assertNotEquals(NETWORK_TYPE_5G_NSA, ratType) } } } diff --git a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java index 5eea0e86eb0e..fdc6084884f6 100644 --- a/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java +++ b/tests/net/java/com/android/server/connectivity/PermissionMonitorTest.java @@ -114,6 +114,7 @@ public class PermissionMonitorTest { @Mock private INetd mNetdService; @Mock private PackageManagerInternal mMockPmi; @Mock private UserManager mUserManager; + @Mock private PermissionMonitor.Dependencies mDeps; private PermissionMonitor mPermissionMonitor; @@ -128,7 +129,7 @@ public class PermissionMonitorTest { new UserInfo(MOCK_USER2, "", 0), })); - mPermissionMonitor = spy(new PermissionMonitor(mContext, mNetdService)); + mPermissionMonitor = spy(new PermissionMonitor(mContext, mNetdService, mDeps)); LocalServices.removeServiceForTest(PackageManagerInternal.class); LocalServices.addService(PackageManagerInternal.class, mMockPmi); @@ -283,14 +284,14 @@ public class PermissionMonitorTest { @Test public void testHasRestrictedNetworkPermissionSystemUid() { - doReturn(VERSION_P).when(mPermissionMonitor).getDeviceFirstSdkInt(); + doReturn(VERSION_P).when(mDeps).getDeviceFirstSdkInt(); assertTrue(hasRestrictedNetworkPermission(PARTITION_SYSTEM, VERSION_P, SYSTEM_UID)); assertTrue(hasRestrictedNetworkPermission( PARTITION_SYSTEM, VERSION_P, SYSTEM_UID, CONNECTIVITY_INTERNAL)); assertTrue(hasRestrictedNetworkPermission( PARTITION_SYSTEM, VERSION_P, SYSTEM_UID, CONNECTIVITY_USE_RESTRICTED_NETWORKS)); - doReturn(VERSION_Q).when(mPermissionMonitor).getDeviceFirstSdkInt(); + doReturn(VERSION_Q).when(mDeps).getDeviceFirstSdkInt(); assertFalse(hasRestrictedNetworkPermission(PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID)); assertFalse(hasRestrictedNetworkPermission( PARTITION_SYSTEM, VERSION_Q, SYSTEM_UID, CONNECTIVITY_INTERNAL)); diff --git a/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java b/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java index c813269744ef..9531b0a5bb66 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsSubscriptionsMonitorTest.java @@ -17,6 +17,7 @@ package com.android.server.net; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; @@ -30,7 +31,9 @@ import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.content.Context; +import android.net.NetworkTemplate; import android.os.test.TestLooper; +import android.telephony.NetworkRegistrationInfo; import android.telephony.PhoneStateListener; import android.telephony.ServiceState; import android.telephony.SubscriptionManager; @@ -61,7 +64,6 @@ public final class NetworkStatsSubscriptionsMonitorTest { private static final String TEST_IMSI3 = "466929999999999"; @Mock private Context mContext; - @Mock private PhoneStateListener mPhoneStateListener; @Mock private SubscriptionManager mSubscriptionManager; @Mock private TelephonyManager mTelephonyManager; @Mock private NetworkStatsSubscriptionsMonitor.Delegate mDelegate; @@ -215,4 +217,55 @@ public final class NetworkStatsSubscriptionsMonitorTest { verify(mTelephonyManager, times(2)).listen(any(), eq(PhoneStateListener.LISTEN_NONE)); assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); } + + + @Test + public void test5g() { + mMonitor.start(); + // Insert sim1, verify RAT type is NETWORK_TYPE_UNKNOWN, and never get any callback + // before changing RAT type. Also capture listener for later use. + addTestSub(TEST_SUBID1, TEST_IMSI1); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UNKNOWN); + final ArgumentCaptor<RatTypeListener> ratTypeListenerCaptor = + ArgumentCaptor.forClass(RatTypeListener.class); + verify(mTelephonyManager, times(1)).listen(ratTypeListenerCaptor.capture(), + eq(PhoneStateListener.LISTEN_SERVICE_STATE)); + final RatTypeListener listener = CollectionUtils + .find(ratTypeListenerCaptor.getAllValues(), it -> it.getSubId() == TEST_SUBID1); + assertNotNull(listener); + + // Set RAT type to 5G NSA (non-standalone) mode, verify the monitor outputs + // NETWORK_TYPE_5G_NSA. + final ServiceState serviceState = mock(ServiceState.class); + when(serviceState.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_LTE); + when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED); + listener.onServiceStateChanged(serviceState); + assertRatTypeChangedForSub(TEST_IMSI1, NetworkTemplate.NETWORK_TYPE_5G_NSA); + reset(mDelegate); + + // Set RAT type to LTE without NR connected, the RAT type should be downgraded to LTE. + when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_NONE); + listener.onServiceStateChanged(serviceState); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_LTE); + reset(mDelegate); + + // Verify NR connected with other RAT type does not take effect. + when(serviceState.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_UMTS); + when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_CONNECTED); + listener.onServiceStateChanged(serviceState); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_UMTS); + reset(mDelegate); + + // Set RAT type to 5G standalone mode, the RAT type should be NR. + setRatTypeForSub(ratTypeListenerCaptor.getAllValues(), TEST_SUBID1, + TelephonyManager.NETWORK_TYPE_NR); + assertRatTypeChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_NR); + reset(mDelegate); + + // Set NR state to none in standalone mode does not change anything. + when(serviceState.getDataNetworkType()).thenReturn(TelephonyManager.NETWORK_TYPE_NR); + when(serviceState.getNrState()).thenReturn(NetworkRegistrationInfo.NR_STATE_NONE); + listener.onServiceStateChanged(serviceState); + assertRatTypeNotChangedForSub(TEST_IMSI1, TelephonyManager.NETWORK_TYPE_NR); + } } |