Merge "Remove module source code from test-api-stubs-docs"
diff --git a/api/test-current.txt b/api/test-current.txt
index 9acacdb..912f33c 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3189,6 +3189,7 @@
field public static final String DYNAMIC_POWER_SAVINGS_DISABLE_THRESHOLD = "dynamic_power_savings_disable_threshold";
field public static final String DYNAMIC_POWER_SAVINGS_ENABLED = "dynamic_power_savings_enabled";
field public static final String HIDDEN_API_BLACKLIST_EXEMPTIONS = "hidden_api_blacklist_exemptions";
+ field public static final String HIDDEN_API_POLICY = "hidden_api_policy";
field public static final String HIDE_ERROR_DIALOGS = "hide_error_dialogs";
field public static final String LOCATION_GLOBAL_KILL_SWITCH = "location_global_kill_switch";
field public static final String LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST = "location_ignore_settings_package_whitelist";
diff --git a/api/test-lint-baseline.txt b/api/test-lint-baseline.txt
index 0287a28..f5ab40a 100644
--- a/api/test-lint-baseline.txt
+++ b/api/test-lint-baseline.txt
@@ -2499,6 +2499,8 @@
NoSettingsProvider: android.provider.Settings.Global#HIDDEN_API_BLACKLIST_EXEMPTIONS:
+NoSettingsProvider: android.provider.Settings.Global#HIDDEN_API_POLICY:
+
NoSettingsProvider: android.provider.Settings.Global#HIDE_ERROR_DIALOGS:
NoSettingsProvider: android.provider.Settings.Global#LOCATION_GLOBAL_KILL_SWITCH:
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 591a714..ce4024a 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -131,7 +131,7 @@
* you use the {@link android.Manifest.permission#REQUEST_COMPANION_RUN_IN_BACKGROUND} and {@link
* android.Manifest.permission#REQUEST_COMPANION_USE_DATA_IN_BACKGROUND} respectively. Note that these
* special capabilities have a negative effect on the device's battery and user's data
- * usage, therefore you should requested them when absolutely necessary.</p>
+ * usage, therefore you should request them when absolutely necessary.</p>
*
* <p>You can call {@link #getAssociations} to get the list of currently associated
* devices, and {@link #disassociate} to remove an association. Consider doing so when the
diff --git a/core/java/android/net/TEST_MAPPING b/core/java/android/net/TEST_MAPPING
new file mode 100644
index 0000000..abac811
--- /dev/null
+++ b/core/java/android/net/TEST_MAPPING
@@ -0,0 +1,20 @@
+{
+ "imports": [
+ {
+ // Also includes cts/tests/tests/net
+ "path": "frameworks/base/tests/net"
+ },
+ {
+ "path": "packages/modules/NetworkStack"
+ },
+ {
+ "path": "packages/modules/CaptivePortalLogin"
+ },
+ {
+ "path": "frameworks/base/packages/Tethering"
+ },
+ {
+ "path": "frameworks/opt/net/wifi"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 9ee8898..d9aae25 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -13278,6 +13278,7 @@
*
* @hide
*/
+ @TestApi
public static final String HIDDEN_API_POLICY = "hidden_api_policy";
/**
diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java
index 38e3b39..4a0bec1 100755
--- a/core/java/android/text/format/DateFormat.java
+++ b/core/java/android/text/format/DateFormat.java
@@ -17,10 +17,14 @@
package android.text.format;
import android.annotation.NonNull;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.icu.text.DateFormatSymbols;
import android.icu.text.DateTimePatternGenerator;
+import android.os.Build;
import android.provider.Settings;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@@ -159,6 +163,16 @@
private static boolean sIs24Hour;
/**
+ * {@link #getBestDateTimePattern(Locale, String)} does not allow non-consecutive repeated
+ * symbol in the skeleton. For example, please use a skeleton of {@code "jmm"} or
+ * {@code "hmma"} instead of {@code "ahmma"} or {@code "jmma"}, because the field 'j' could
+ * mean using 12-hour in some locales and, in this case, is duplicated as the 'a' field.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
+ static final long DISALLOW_DUPLICATE_FIELD_IN_SKELETON = 170233598L;
+
+ /**
* Returns true if times should be formatted as 24 hour times, false if times should be
* formatted as 12 hour (AM/PM) times. Based on the user's chosen locale and other preferences.
* @param context the context to use for the content resolver
@@ -251,7 +265,9 @@
*/
public static String getBestDateTimePattern(Locale locale, String skeleton) {
DateTimePatternGenerator dtpg = DateTimePatternGenerator.getInstance(locale);
- return dtpg.getBestPattern(skeleton);
+ boolean allowDuplicateFields = !CompatChanges.isChangeEnabled(
+ DISALLOW_DUPLICATE_FIELD_IN_SKELETON);
+ return dtpg.getBestPattern(skeleton, allowDuplicateFields);
}
/**
diff --git a/core/java/android/uwb/UwbAddress.java b/core/java/android/uwb/UwbAddress.java
new file mode 100644
index 0000000..48fcb10e
--- /dev/null
+++ b/core/java/android/uwb/UwbAddress.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 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.
+ */
+
+package android.uwb;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+/**
+ * A class representing a UWB address
+ *
+ * @hide
+ */
+public final class UwbAddress {
+ public static final int SHORT_ADDRESS_BYTE_LENGTH = 2;
+ public static final int EXTENDED_ADDRESS_BYTE_LENGTH = 8;
+
+ /**
+ * Create a {@link UwbAddress} from a byte array.
+ *
+ * <p>If the provided array is {@link #SHORT_ADDRESS_BYTE_LENGTH} bytes, a short address is
+ * created. If the provided array is {@link #EXTENDED_ADDRESS_BYTE_LENGTH} bytes, then an
+ * extended address is created.
+ *
+ * @param address a byte array to convert to a {@link UwbAddress}
+ * @return a {@link UwbAddress} created from the input byte array
+ * @throw IllegableArumentException when the length is not one of
+ * {@link #SHORT_ADDRESS_BYTE_LENGTH} or {@link #EXTENDED_ADDRESS_BYTE_LENGTH} bytes
+ */
+ @NonNull
+ public static UwbAddress fromBytes(byte[] address) throws IllegalArgumentException {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * Get the address as a byte array
+ *
+ * @return the byte representation of this {@link UwbAddress}
+ */
+ @NonNull
+ public byte[] toBytes() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
+ * The length of the address in bytes
+ * <p>Possible values are {@link #SHORT_ADDRESS_BYTE_LENGTH} and
+ * {@link #EXTENDED_ADDRESS_BYTE_LENGTH}.
+ */
+ public int size() {
+ throw new UnsupportedOperationException();
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int hashCode() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 233231c..16991b4 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1281,7 +1281,7 @@
private void safelyStartActivityInternal(TargetInfo cti) {
// If the target is suspended, the activity will not be successfully launched.
// Do not unregister from package manager updates in this case
- if (!cti.isSuspended()) {
+ if (!cti.isSuspended() && mRegistered) {
if (mPersonalPackageMonitor != null) {
mPersonalPackageMonitor.unregister();
}
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index cc94d6f..4220c1d 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -321,7 +321,7 @@
jboolean(focusEvent->getHasFocus()),
jboolean(focusEvent->getInTouchMode()));
finishInputEvent(seq, true /* handled */);
- return OK;
+ continue;
}
default:
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e0b58dd..b9199f7 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1780,6 +1780,9 @@
Note: This config is deprecated, please use config_defaultSms instead. -->
<string name="default_sms_application" translatable="false">com.android.messaging</string>
+ <!-- Flag indicating whether the current device supports "Ask every time" for sms-->
+ <bool name="config_sms_ask_every_time_support">true</bool>
+
<!-- Flag indicating whether the current device allows data.
If true, this means that the device supports data connectivity through
the telephony network.
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b130b91..f9a7ba0 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -312,6 +312,7 @@
<java-symbol type="bool" name="config_networkSamplingWakesDevice" />
<java-symbol type="bool" name="config_showMenuShortcutsWhenKeyboardPresent" />
<java-symbol type="bool" name="config_sip_wifi_only" />
+ <java-symbol type="bool" name="config_sms_ask_every_time_support" />
<java-symbol type="bool" name="config_sms_capable" />
<java-symbol type="bool" name="config_sms_utf8_support" />
<java-symbol type="bool" name="config_mobile_data_capable" />
diff --git a/core/sysprop/Android.bp b/core/sysprop/Android.bp
index 7f20a0b..237ede2 100644
--- a/core/sysprop/Android.bp
+++ b/core/sysprop/Android.bp
@@ -19,3 +19,11 @@
api_packages: ["android.sysprop"],
vendor_available: false,
}
+
+sysprop_library {
+ name: "com.android.sysprop.watchdog",
+ srcs: ["WatchdogProperties.sysprop"],
+ property_owner: "Platform",
+ api_packages: ["android.sysprop"],
+ vendor_available: false,
+}
diff --git a/core/sysprop/WatchdogProperties.sysprop b/core/sysprop/WatchdogProperties.sysprop
new file mode 100644
index 0000000..1bcc773
--- /dev/null
+++ b/core/sysprop/WatchdogProperties.sysprop
@@ -0,0 +1,45 @@
+# 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.
+
+module: "android.sysprop.WatchdogProperties"
+owner: Platform
+
+# To escape the watchdog timeout loop, fatal reboot the system when
+# watchdog timed out 'fatal_count' times in 'fatal_window_second'
+# seconds, if both values are not 0. Default value of both is 0.
+prop {
+ api_name: "fatal_count"
+ type: Integer
+ prop_name: "framework_watchdog.fatal_count"
+ scope: Internal
+ access: Readonly
+}
+
+prop {
+ api_name: "fatal_window_second"
+ type: Integer
+ prop_name: "framework_watchdog.fatal_window.second"
+ scope: Internal
+ access: Readonly
+}
+
+# The fatal counting can be disabled by setting property
+# 'is_fatal_ignore' to true.
+prop {
+ api_name: "is_fatal_ignore"
+ type: Boolean
+ prop_name: "persist.debug.framework_watchdog.fatal_ignore"
+ scope: Internal
+ access: Readonly
+}
diff --git a/core/sysprop/api/com.android.sysprop.localization-current.txt b/core/sysprop/api/com.android.sysprop.localization-current.txt
index fe4f457..e69de29 100644
--- a/core/sysprop/api/com.android.sysprop.localization-current.txt
+++ b/core/sysprop/api/com.android.sysprop.localization-current.txt
@@ -1,9 +0,0 @@
-props {
- module: "android.sysprop.LocalizationProperties"
- prop {
- api_name: "locale_filter"
- type: String
- scope: Internal
- prop_name: "ro.localization.locale_filter"
- }
-}
diff --git a/core/sysprop/api/com.android.sysprop.watchdog-current.txt b/core/sysprop/api/com.android.sysprop.watchdog-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/sysprop/api/com.android.sysprop.watchdog-current.txt
diff --git a/core/sysprop/api/com.android.sysprop.watchdog-latest.txt b/core/sysprop/api/com.android.sysprop.watchdog-latest.txt
new file mode 100644
index 0000000..d901aef
--- /dev/null
+++ b/core/sysprop/api/com.android.sysprop.watchdog-latest.txt
@@ -0,0 +1,20 @@
+props {
+ module: "android.sysprop.WatchdogProperties"
+ prop {
+ api_name: "fatal_count"
+ type: Integer
+ scope: Internal
+ prop_name: "framework_watchdog.fatal_count"
+ }
+ prop {
+ api_name: "fatal_window_second"
+ type: Integer
+ scope: Internal
+ prop_name: "framework_watchdog.fatal_window.second"
+ }
+ prop {
+ api_name: "is_fatal_ignore"
+ scope: Internal
+ prop_name: "persist.debug.framework_watchdog.fatal_ignore"
+ }
+}
diff --git a/core/tests/coretests/src/android/text/format/DateFormatTest.java b/core/tests/coretests/src/android/text/format/DateFormatTest.java
index a3434e8..212cc44 100644
--- a/core/tests/coretests/src/android/text/format/DateFormatTest.java
+++ b/core/tests/coretests/src/android/text/format/DateFormatTest.java
@@ -21,13 +21,19 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.compat.testing.PlatformCompatChangeRule;
import android.icu.text.DateFormatSymbols;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import java.util.Arrays;
@@ -38,6 +44,9 @@
@RunWith(AndroidJUnit4.class)
public class DateFormatTest {
+ @Rule
+ public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
@Test
public void testHasDesignator() {
assertTrue(DateFormat.hasDesignator("hh:mm:ss", DateFormat.MINUTE));
@@ -135,4 +144,29 @@
private static String best(Locale l, String skeleton) {
return DateFormat.getBestDateTimePattern(l, skeleton);
}
+
+ @Test
+ @EnableCompatChanges({DateFormat.DISALLOW_DUPLICATE_FIELD_IN_SKELETON})
+ public void testGetBestDateTimePattern_disableDuplicateField() {
+ assertIllegalArgumentException(Locale.US, "jmma");
+ assertIllegalArgumentException(Locale.US, "ahmma");
+ }
+
+ @Test
+ @DisableCompatChanges({DateFormat.DISALLOW_DUPLICATE_FIELD_IN_SKELETON})
+ public void testGetBestDateTimePattern_enableDuplicateField() {
+ // en-US uses 12-hour format by default.
+ assertEquals("h:mm a", DateFormat.getBestDateTimePattern(Locale.US, "jmma"));
+ assertEquals("h:mm a", DateFormat.getBestDateTimePattern(Locale.US, "ahmma"));
+ }
+
+ private static void assertIllegalArgumentException(Locale l, String skeleton) {
+ try {
+ DateFormat.getBestDateTimePattern(l, skeleton);
+ fail("getBestDateTimePattern() does not fail with Locale: " + l
+ + " skeleton: " + skeleton);
+ } catch (IllegalArgumentException expected) {
+ // ignored
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/text/format/OWNERS b/core/tests/coretests/src/android/text/format/OWNERS
new file mode 100644
index 0000000..32adc12
--- /dev/null
+++ b/core/tests/coretests/src/android/text/format/OWNERS
@@ -0,0 +1,3 @@
+# Inherits OWNERS from parent directory, plus the following
+
+vichang@google.com
diff --git a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
index 67a040d..139474c 100644
--- a/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
+++ b/location/java/com/android/internal/location/GpsNetInitiatedHandler.java
@@ -28,7 +28,6 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.telephony.PhoneNumberUtils;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
import android.util.Log;
@@ -161,7 +160,7 @@
be set to true when the phone is having emergency call, and then will
be set to false by mPhoneStateListener when the emergency call ends.
*/
- mIsInEmergencyCall = PhoneNumberUtils.isEmergencyNumber(phoneNumber);
+ mIsInEmergencyCall = mTelephonyManager.isEmergencyNumber(phoneNumber);
if (DEBUG) Log.v(TAG, "ACTION_NEW_OUTGOING_CALL - " + getInEmergency());
} else if (action.equals(LocationManager.MODE_CHANGED_ACTION)) {
updateLocationMode();
diff --git a/media/java/android/media/AudioTrack.java b/media/java/android/media/AudioTrack.java
index 1c0a526..eff56f3 100644
--- a/media/java/android/media/AudioTrack.java
+++ b/media/java/android/media/AudioTrack.java
@@ -1261,14 +1261,20 @@
// TODO: Check mEncapsulationMode compatibility with MODE_STATIC, etc?
- try {
- // If the buffer size is not specified in streaming mode,
- // use a single frame for the buffer size and let the
- // native code figure out the minimum buffer size.
- if (mMode == MODE_STREAM && mBufferSizeInBytes == 0) {
- mBufferSizeInBytes = mFormat.getChannelCount()
- * mFormat.getBytesPerSample(mFormat.getEncoding());
+ // If the buffer size is not specified in streaming mode,
+ // use a single frame for the buffer size and let the
+ // native code figure out the minimum buffer size.
+ if (mMode == MODE_STREAM && mBufferSizeInBytes == 0) {
+ int bytesPerSample = 1;
+ try {
+ bytesPerSample = mFormat.getBytesPerSample(mFormat.getEncoding());
+ } catch (IllegalArgumentException e) {
+ // do nothing
}
+ mBufferSizeInBytes = mFormat.getChannelCount() * bytesPerSample;
+ }
+
+ try {
final AudioTrack track = new AudioTrack(
mAttributes, mFormat, mBufferSizeInBytes, mMode, mSessionId, mOffload,
mEncapsulationMode, mTunerConfiguration);
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index 1a9abb9..e6f43c1 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -132,7 +132,7 @@
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
mMediaRecorder.setVideoEncodingProfileLevel(
MediaCodecInfo.CodecProfileLevel.AVCProfileHigh,
- MediaCodecInfo.CodecProfileLevel.AVCLevel42);
+ MediaCodecInfo.CodecProfileLevel.AVCLevel3);
mMediaRecorder.setVideoSize(screenWidth, screenHeight);
mMediaRecorder.setVideoFrameRate(refereshRate);
mMediaRecorder.setVideoEncodingBitRate(vidBitRate);
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java b/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
index 0cf14e3..9fc1d7e 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/PrivateAddressCoordinator.java
@@ -36,6 +36,7 @@
import android.util.ArraySet;
import android.util.SparseArray;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
@@ -135,7 +136,6 @@
private void handleMaybePrefixConflict(final List<IpPrefix> prefixes) {
for (IpServer downstream : mDownstreams) {
final IpPrefix target = getDownstreamPrefix(downstream);
- if (target == null) continue;
for (IpPrefix source : prefixes) {
if (isConflictPrefix(source, target)) {
@@ -179,6 +179,7 @@
final LinkAddress cachedAddress = mCachedAddresses.get(ipServer.interfaceType());
if (useLastAddress && cachedAddress != null
&& !isConflictWithUpstream(asIpPrefix(cachedAddress))) {
+ mDownstreams.add(ipServer);
return cachedAddress;
}
@@ -370,7 +371,6 @@
// in mCachedAddresses.
for (IpServer downstream : mDownstreams) {
final IpPrefix target = getDownstreamPrefix(downstream);
- if (target == null) continue;
if (isConflictPrefix(prefix, target)) return target;
}
@@ -378,9 +378,9 @@
return null;
}
+ @NonNull
private IpPrefix getDownstreamPrefix(final IpServer downstream) {
final LinkAddress address = downstream.getAddress();
- if (address == null) return null;
return asIpPrefix(address);
}
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
index da13e34..8cb80ba 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/PrivateAddressCoordinatorTest.java
@@ -104,24 +104,30 @@
mTetheringPrefixes));
}
+ private LinkAddress requestDownstreamAddress(final IpServer ipServer, boolean useLastAddress) {
+ final LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
+ ipServer, useLastAddress);
+ when(ipServer.getAddress()).thenReturn(address);
+ return address;
+ }
+
@Test
public void testRequestDownstreamAddressWithoutUsingLastAddress() throws Exception {
final IpPrefix bluetoothPrefix = asIpPrefix(mBluetoothAddress);
- final LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, false /* useLastAddress */);
+ final LinkAddress address = requestDownstreamAddress(mHotspotIpServer,
+ false /* useLastAddress */);
final IpPrefix hotspotPrefix = asIpPrefix(address);
assertNotEquals(hotspotPrefix, bluetoothPrefix);
- when(mHotspotIpServer.getAddress()).thenReturn(address);
- final LinkAddress newAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, false /* useLastAddress */);
+ final LinkAddress newAddress = requestDownstreamAddress(mHotspotIpServer,
+ false /* useLastAddress */);
final IpPrefix testDupRequest = asIpPrefix(newAddress);
assertNotEquals(hotspotPrefix, testDupRequest);
assertNotEquals(bluetoothPrefix, testDupRequest);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
- final LinkAddress usbAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
- mUsbIpServer, false /* useLastAddress */);
+ final LinkAddress usbAddress = requestDownstreamAddress(mUsbIpServer,
+ false /* useLastAddress */);
final IpPrefix usbPrefix = asIpPrefix(usbAddress);
assertNotEquals(usbPrefix, bluetoothPrefix);
assertNotEquals(usbPrefix, hotspotPrefix);
@@ -132,29 +138,26 @@
public void testSanitizedAddress() throws Exception {
int fakeSubAddr = 0x2b00; // 43.0.
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
- LinkAddress actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, false /* useLastAddress */);
+ LinkAddress actualAddress = requestDownstreamAddress(mHotspotIpServer,
+ false /* useLastAddress */);
assertEquals(new LinkAddress("192.168.43.2/24"), actualAddress);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
fakeSubAddr = 0x2d01; // 45.1.
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
- actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, false /* useLastAddress */);
+ actualAddress = requestDownstreamAddress(mHotspotIpServer, false /* useLastAddress */);
assertEquals(new LinkAddress("192.168.45.2/24"), actualAddress);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
fakeSubAddr = 0x2eff; // 46.255.
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
- actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, false /* useLastAddress */);
+ actualAddress = requestDownstreamAddress(mHotspotIpServer, false /* useLastAddress */);
assertEquals(new LinkAddress("192.168.46.254/24"), actualAddress);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
fakeSubAddr = 0x2f05; // 47.5.
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeSubAddr);
- actualAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, false /* useLastAddress */);
+ actualAddress = requestDownstreamAddress(mHotspotIpServer, false /* useLastAddress */);
assertEquals(new LinkAddress("192.168.47.5/24"), actualAddress);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
}
@@ -164,8 +167,8 @@
// - Test bluetooth prefix is reserved.
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
getSubAddress(mBluetoothAddress.getAddress().getAddress()));
- final LinkAddress hotspotAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, false /* useLastAddress */);
+ final LinkAddress hotspotAddress = requestDownstreamAddress(mHotspotIpServer,
+ false /* useLastAddress */);
final IpPrefix hotspotPrefix = asIpPrefix(hotspotAddress);
assertNotEquals(asIpPrefix(mBluetoothAddress), hotspotPrefix);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
@@ -173,8 +176,8 @@
// - Test previous enabled hotspot prefix(cached prefix) is reserved.
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
getSubAddress(hotspotAddress.getAddress().getAddress()));
- final LinkAddress usbAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
- mUsbIpServer, false /* useLastAddress */);
+ final LinkAddress usbAddress = requestDownstreamAddress(mUsbIpServer,
+ false /* useLastAddress */);
final IpPrefix usbPrefix = asIpPrefix(usbAddress);
assertNotEquals(asIpPrefix(mBluetoothAddress), usbPrefix);
assertNotEquals(hotspotPrefix, usbPrefix);
@@ -183,8 +186,8 @@
// - Test wifi p2p prefix is reserved.
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(
getSubAddress(mLegacyWifiP2pAddress.getAddress().getAddress()));
- final LinkAddress etherAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
- mEthernetIpServer, false /* useLastAddress */);
+ final LinkAddress etherAddress = requestDownstreamAddress(mEthernetIpServer,
+ false /* useLastAddress */);
final IpPrefix etherPrefix = asIpPrefix(etherAddress);
assertNotEquals(asIpPrefix(mLegacyWifiP2pAddress), etherPrefix);
assertNotEquals(asIpPrefix(mBluetoothAddress), etherPrefix);
@@ -196,13 +199,12 @@
public void testRequestLastDownstreamAddress() throws Exception {
final int fakeHotspotSubAddr = 0x2b05; // 43.5
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr);
- final LinkAddress hotspotAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, true /* useLastAddress */);
+ final LinkAddress hotspotAddress = requestDownstreamAddress(mHotspotIpServer,
+ true /* useLastAddress */);
assertEquals("Wrong wifi prefix: ", new LinkAddress("192.168.43.5/24"), hotspotAddress);
- when(mHotspotIpServer.getAddress()).thenReturn(hotspotAddress);
- final LinkAddress usbAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
- mUsbIpServer, true /* useLastAddress */);
+ final LinkAddress usbAddress = requestDownstreamAddress(mUsbIpServer,
+ true /* useLastAddress */);
assertEquals("Wrong wifi prefix: ", new LinkAddress("192.168.45.5/24"), usbAddress);
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
@@ -211,24 +213,19 @@
final int newFakeSubAddr = 0x3c05;
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr);
- final LinkAddress newHotspotAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, true /* useLastAddress */);
+ final LinkAddress newHotspotAddress = requestDownstreamAddress(mHotspotIpServer,
+ true /* useLastAddress */);
assertEquals(hotspotAddress, newHotspotAddress);
- final LinkAddress newUsbAddress = mPrivateAddressCoordinator.requestDownstreamAddress(
- mUsbIpServer, true /* useLastAddress */);
+ final LinkAddress newUsbAddress = requestDownstreamAddress(mUsbIpServer,
+ true /* useLastAddress */);
assertEquals(usbAddress, newUsbAddress);
- // BUG: the code should detect a conflict, but it doesn't.
- // Regression introduced in r.android.com/168169687.
- // Ensure conflict notification works when using cached address.
- when(mHotspotIpServer.getAddress()).thenReturn(newHotspotAddress);
- when(mUsbIpServer.getAddress()).thenReturn(usbAddress);
final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
new LinkAddress("192.168.88.23/16"), null,
makeNetworkCapabilities(TRANSPORT_WIFI));
mPrivateAddressCoordinator.updateUpstreamPrefix(wifiUpstream);
- verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
- verify(mUsbIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
+ verify(mHotspotIpServer).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
+ verify(mUsbIpServer).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
}
private UpstreamNetworkState buildUpstreamNetworkState(final Network network,
@@ -259,11 +256,10 @@
// Force always get subAddress "43.5" for conflict testing.
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(fakeHotspotSubAddr);
// - Enable hotspot with prefix 192.168.43.0/24
- final LinkAddress hotspotAddr = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, true /* useLastAddress */);
+ final LinkAddress hotspotAddr = requestDownstreamAddress(mHotspotIpServer,
+ true /* useLastAddress */);
final IpPrefix hotspotPrefix = asIpPrefix(hotspotAddr);
assertEquals("Wrong wifi prefix: ", predefinedPrefix, hotspotPrefix);
- when(mHotspotIpServer.getAddress()).thenReturn(hotspotAddr);
// - test mobile network with null NetworkCapabilities. Ideally this should not happen
// because NetworkCapabilities update should always happen before LinkProperties update
// and the UpstreamNetworkState update, just make sure no crash in this case.
@@ -314,24 +310,22 @@
reset(mHotspotIpServer);
// - Restart hotspot again and its prefix is different previous.
mPrivateAddressCoordinator.releaseDownstream(mHotspotIpServer);
- final LinkAddress hotspotAddr2 = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, true /* useLastAddress */);
+ final LinkAddress hotspotAddr2 = requestDownstreamAddress(mHotspotIpServer,
+ true /* useLastAddress */);
final IpPrefix hotspotPrefix2 = asIpPrefix(hotspotAddr2);
assertNotEquals(hotspotPrefix, hotspotPrefix2);
- when(mHotspotIpServer.getAddress()).thenReturn(hotspotAddr2);
mPrivateAddressCoordinator.updateUpstreamPrefix(v4OnlyWifi);
verify(mHotspotIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
// - Usb tethering can be enabled and its prefix is different with conflict one.
- final LinkAddress usbAddr = mPrivateAddressCoordinator.requestDownstreamAddress(
- mUsbIpServer, true /* useLastAddress */);
+ final LinkAddress usbAddr = requestDownstreamAddress(mUsbIpServer,
+ true /* useLastAddress */);
final IpPrefix usbPrefix = asIpPrefix(usbAddr);
assertNotEquals(predefinedPrefix, usbPrefix);
assertNotEquals(hotspotPrefix2, usbPrefix);
- when(mUsbIpServer.getAddress()).thenReturn(usbAddr);
// - Disable wifi upstream, then wifi's prefix can be selected again.
mPrivateAddressCoordinator.removeUpstreamPrefix(mWifiNetwork);
- final LinkAddress ethAddr = mPrivateAddressCoordinator.requestDownstreamAddress(
- mEthernetIpServer, true /* useLastAddress */);
+ final LinkAddress ethAddr = requestDownstreamAddress(mEthernetIpServer,
+ true /* useLastAddress */);
final IpPrefix ethPrefix = asIpPrefix(ethAddr);
assertEquals(predefinedPrefix, ethPrefix);
}
@@ -340,21 +334,19 @@
public void testChooseAvailablePrefix() throws Exception {
final int randomAddress = 0x8605; // 134.5
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(randomAddress);
- final LinkAddress addr0 = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, true/* useLastAddress */);
+ final LinkAddress addr0 = requestDownstreamAddress(mHotspotIpServer,
+ true /* useLastAddress */);
// Check whether return address is prefix 192.168.0.0/16 + subAddress 0.0.134.5.
assertEquals("Wrong prefix: ", new LinkAddress("192.168.134.5/24"), addr0);
- when(mHotspotIpServer.getAddress()).thenReturn(addr0);
final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
new LinkAddress("192.168.134.13/26"), null,
makeNetworkCapabilities(TRANSPORT_WIFI));
mPrivateAddressCoordinator.updateUpstreamPrefix(wifiUpstream);
// Check whether return address is next prefix of 192.168.134.0/24.
- final LinkAddress addr1 = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, true/* useLastAddress */);
+ final LinkAddress addr1 = requestDownstreamAddress(mHotspotIpServer,
+ true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("192.168.135.5/24"), addr1);
- when(mHotspotIpServer.getAddress()).thenReturn(addr1);
final UpstreamNetworkState wifiUpstream2 = buildUpstreamNetworkState(mWifiNetwork,
new LinkAddress("192.168.149.16/19"), null,
makeNetworkCapabilities(TRANSPORT_WIFI));
@@ -362,10 +354,9 @@
// The conflict range is 128 ~ 159, so the address is 192.168.160.5/24.
- final LinkAddress addr2 = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, true/* useLastAddress */);
+ final LinkAddress addr2 = requestDownstreamAddress(mHotspotIpServer,
+ true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("192.168.160.5/24"), addr2);
- when(mHotspotIpServer.getAddress()).thenReturn(addr2);
final UpstreamNetworkState mobileUpstream = buildUpstreamNetworkState(mMobileNetwork,
new LinkAddress("192.168.129.53/18"), null,
makeNetworkCapabilities(TRANSPORT_CELLULAR));
@@ -378,10 +369,9 @@
mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream2);
// The conflict range are 128 ~ 159 and 159 ~ 191, so the address is 192.168.192.5/24.
- final LinkAddress addr3 = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, true/* useLastAddress */);
+ final LinkAddress addr3 = requestDownstreamAddress(mHotspotIpServer,
+ true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("192.168.192.5/24"), addr3);
- when(mHotspotIpServer.getAddress()).thenReturn(addr3);
final UpstreamNetworkState mobileUpstream3 = buildUpstreamNetworkState(mMobileNetwork3,
new LinkAddress("192.168.188.133/17"), null,
makeNetworkCapabilities(TRANSPORT_CELLULAR));
@@ -389,20 +379,18 @@
// Conflict range: 128 ~ 255. The next available address is 192.168.0.5 because
// 192.168.134/24 ~ 192.168.255.255/24 is not available.
- final LinkAddress addr4 = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, true/* useLastAddress */);
+ final LinkAddress addr4 = requestDownstreamAddress(mHotspotIpServer,
+ true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("192.168.0.5/24"), addr4);
- when(mHotspotIpServer.getAddress()).thenReturn(addr4);
final UpstreamNetworkState mobileUpstream4 = buildUpstreamNetworkState(mMobileNetwork4,
new LinkAddress("192.168.3.59/21"), null,
makeNetworkCapabilities(TRANSPORT_CELLULAR));
mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream4);
// Conflict ranges: 128 ~ 255 and 0 ~ 7, so the address is 192.168.8.5/24.
- final LinkAddress addr5 = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, true/* useLastAddress */);
+ final LinkAddress addr5 = requestDownstreamAddress(mHotspotIpServer,
+ true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("192.168.8.5/24"), addr5);
- when(mHotspotIpServer.getAddress()).thenReturn(addr5);
final UpstreamNetworkState mobileUpstream5 = buildUpstreamNetworkState(mMobileNetwork5,
new LinkAddress("192.168.68.43/21"), null,
makeNetworkCapabilities(TRANSPORT_CELLULAR));
@@ -410,41 +398,37 @@
// Update an upstream that does *not* conflict, check whether return the same address
// 192.168.5/24.
- final LinkAddress addr6 = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, true/* useLastAddress */);
+ final LinkAddress addr6 = requestDownstreamAddress(mHotspotIpServer,
+ true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("192.168.8.5/24"), addr6);
- when(mHotspotIpServer.getAddress()).thenReturn(addr6);
final UpstreamNetworkState mobileUpstream6 = buildUpstreamNetworkState(mMobileNetwork6,
new LinkAddress("192.168.10.97/21"), null,
makeNetworkCapabilities(TRANSPORT_CELLULAR));
mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream6);
// Conflict ranges: 0 ~ 15 and 128 ~ 255, so the address is 192.168.16.5/24.
- final LinkAddress addr7 = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, true/* useLastAddress */);
+ final LinkAddress addr7 = requestDownstreamAddress(mHotspotIpServer,
+ true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("192.168.16.5/24"), addr7);
- when(mHotspotIpServer.getAddress()).thenReturn(addr7);
final UpstreamNetworkState mobileUpstream7 = buildUpstreamNetworkState(mMobileNetwork6,
new LinkAddress("192.168.0.0/17"), null,
makeNetworkCapabilities(TRANSPORT_CELLULAR));
mPrivateAddressCoordinator.updateUpstreamPrefix(mobileUpstream7);
// Choose prefix from next range(172.16.0.0/12) when no available prefix in 192.168.0.0/16.
- final LinkAddress addr8 = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, true/* useLastAddress */);
+ final LinkAddress addr8 = requestDownstreamAddress(mHotspotIpServer,
+ true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("172.16.134.5/24"), addr8);
- when(mHotspotIpServer.getAddress()).thenReturn(addr6);
}
@Test
public void testChoosePrefixFromDifferentRanges() throws Exception {
final int randomAddress = 0x1f2b2a; // 31.43.42
when(mPrivateAddressCoordinator.getRandomInt()).thenReturn(randomAddress);
- final LinkAddress classC1 = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, true/* useLastAddress */);
+ final LinkAddress classC1 = requestDownstreamAddress(mHotspotIpServer,
+ true /* useLastAddress */);
// Check whether return address is prefix 192.168.0.0/16 + subAddress 0.0.43.42.
assertEquals("Wrong prefix: ", new LinkAddress("192.168.43.42/24"), classC1);
- when(mHotspotIpServer.getAddress()).thenReturn(classC1);
final UpstreamNetworkState wifiUpstream = buildUpstreamNetworkState(mWifiNetwork,
new LinkAddress("192.168.88.23/17"), null,
makeNetworkCapabilities(TRANSPORT_WIFI));
@@ -452,10 +436,9 @@
verifyNotifyConflictAndRelease(mHotspotIpServer);
// Check whether return address is next address of prefix 192.168.128.0/17.
- final LinkAddress classC2 = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, true/* useLastAddress */);
+ final LinkAddress classC2 = requestDownstreamAddress(mHotspotIpServer,
+ true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("192.168.128.42/24"), classC2);
- when(mHotspotIpServer.getAddress()).thenReturn(classC2);
final UpstreamNetworkState mobileUpstream = buildUpstreamNetworkState(mMobileNetwork,
new LinkAddress("192.1.2.3/8"), null,
makeNetworkCapabilities(TRANSPORT_CELLULAR));
@@ -463,10 +446,9 @@
verifyNotifyConflictAndRelease(mHotspotIpServer);
// Check whether return address is under prefix 172.16.0.0/12.
- final LinkAddress classB1 = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, true/* useLastAddress */);
+ final LinkAddress classB1 = requestDownstreamAddress(mHotspotIpServer,
+ true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("172.31.43.42/24"), classB1);
- when(mHotspotIpServer.getAddress()).thenReturn(classB1);
final UpstreamNetworkState mobileUpstream2 = buildUpstreamNetworkState(mMobileNetwork2,
new LinkAddress("172.28.123.100/14"), null,
makeNetworkCapabilities(TRANSPORT_CELLULAR));
@@ -475,16 +457,14 @@
// 172.28.0.0 ~ 172.31.255.255 is not available.
// Check whether return address is next address of prefix 172.16.0.0/14.
- final LinkAddress classB2 = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, true/* useLastAddress */);
+ final LinkAddress classB2 = requestDownstreamAddress(mHotspotIpServer,
+ true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("172.16.0.42/24"), classB2);
- when(mHotspotIpServer.getAddress()).thenReturn(classB2);
// Check whether new downstream is next address of address 172.16.0.42/24.
- final LinkAddress classB3 = mPrivateAddressCoordinator.requestDownstreamAddress(
- mUsbIpServer, true/* useLastAddress */);
+ final LinkAddress classB3 = requestDownstreamAddress(mUsbIpServer,
+ true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("172.16.1.42/24"), classB3);
- when(mUsbIpServer.getAddress()).thenReturn(classB3);
final UpstreamNetworkState mobileUpstream3 = buildUpstreamNetworkState(mMobileNetwork3,
new LinkAddress("172.16.0.1/24"), null,
makeNetworkCapabilities(TRANSPORT_CELLULAR));
@@ -493,10 +473,9 @@
verify(mUsbIpServer, never()).sendMessage(IpServer.CMD_NOTIFY_PREFIX_CONFLICT);
// Check whether return address is next address of prefix 172.16.1.42/24.
- final LinkAddress classB4 = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, true/* useLastAddress */);
+ final LinkAddress classB4 = requestDownstreamAddress(mHotspotIpServer,
+ true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("172.16.2.42/24"), classB4);
- when(mHotspotIpServer.getAddress()).thenReturn(classB4);
final UpstreamNetworkState mobileUpstream4 = buildUpstreamNetworkState(mMobileNetwork4,
new LinkAddress("172.16.0.1/13"), null,
makeNetworkCapabilities(TRANSPORT_CELLULAR));
@@ -505,15 +484,13 @@
verifyNotifyConflictAndRelease(mUsbIpServer);
// Check whether return address is next address of prefix 172.16.0.1/13.
- final LinkAddress classB5 = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, true/* useLastAddress */);
+ final LinkAddress classB5 = requestDownstreamAddress(mHotspotIpServer,
+ true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("172.24.0.42/24"), classB5);
- when(mHotspotIpServer.getAddress()).thenReturn(classB5);
// Check whether return address is next address of prefix 172.24.0.42/24.
- final LinkAddress classB6 = mPrivateAddressCoordinator.requestDownstreamAddress(
- mUsbIpServer, true/* useLastAddress */);
+ final LinkAddress classB6 = requestDownstreamAddress(mUsbIpServer,
+ true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("172.24.1.42/24"), classB6);
- when(mUsbIpServer.getAddress()).thenReturn(classB6);
final UpstreamNetworkState mobileUpstream5 = buildUpstreamNetworkState(mMobileNetwork5,
new LinkAddress("172.24.0.1/12"), null,
makeNetworkCapabilities(TRANSPORT_CELLULAR));
@@ -522,13 +499,12 @@
verifyNotifyConflictAndRelease(mUsbIpServer);
// Check whether return address is prefix 10.0.0.0/8 + subAddress 0.31.43.42.
- final LinkAddress classA1 = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, true/* useLastAddress */);
+ final LinkAddress classA1 = requestDownstreamAddress(mHotspotIpServer,
+ true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("10.31.43.42/24"), classA1);
- when(mHotspotIpServer.getAddress()).thenReturn(classA1);
// Check whether new downstream is next address of address 10.31.43.42/24.
- final LinkAddress classA2 = mPrivateAddressCoordinator.requestDownstreamAddress(
- mUsbIpServer, true/* useLastAddress */);
+ final LinkAddress classA2 = requestDownstreamAddress(mUsbIpServer,
+ true /* useLastAddress */);
assertEquals("Wrong prefix: ", new LinkAddress("10.31.44.42/24"), classA2);
}
@@ -547,8 +523,8 @@
}
private void assertReseveredWifiP2pPrefix() throws Exception {
- LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
- mHotspotIpServer, true /* useLastAddress */);
+ LinkAddress address = requestDownstreamAddress(mHotspotIpServer,
+ true /* useLastAddress */);
final IpPrefix hotspotPrefix = asIpPrefix(address);
final IpPrefix legacyWifiP2pPrefix = asIpPrefix(mLegacyWifiP2pAddress);
assertNotEquals(legacyWifiP2pPrefix, hotspotPrefix);
@@ -567,8 +543,8 @@
assertReseveredWifiP2pPrefix();
// If #shouldEnableWifiP2pDedicatedIp() is enabled, wifi P2P gets the configured address.
- LinkAddress address = mPrivateAddressCoordinator.requestDownstreamAddress(
- mWifiP2pIpServer, true /* useLastAddress */);
+ LinkAddress address = requestDownstreamAddress(mWifiP2pIpServer,
+ true /* useLastAddress */);
assertEquals(mLegacyWifiP2pAddress, address);
mPrivateAddressCoordinator.releaseDownstream(mWifiP2pIpServer);
}
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 776c8f5..431555b 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -132,6 +132,7 @@
"netd_aidl_interfaces-platform-java",
"overlayable_policy_aidl-java",
"SurfaceFlingerProperties",
+ "com.android.sysprop.watchdog",
],
}
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 17c0970..418deb8 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -23,7 +23,9 @@
import android.content.IntentFilter;
import android.hidl.manager.V1_0.IServiceManager;
import android.os.Binder;
+import android.os.Build;
import android.os.Debug;
+import android.os.FileUtils;
import android.os.Handler;
import android.os.IPowerManager;
import android.os.Looper;
@@ -31,10 +33,12 @@
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
+import android.os.SystemProperties;
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
+import android.sysprop.WatchdogProperties;
import com.android.internal.os.ProcessCpuTracker;
import com.android.internal.os.ZygoteConnectionConstants;
@@ -42,12 +46,16 @@
import com.android.server.am.ActivityManagerService;
import com.android.server.wm.SurfaceAnimationThread;
+import java.io.BufferedReader;
import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
import java.util.HashSet;
import java.util.List;
@@ -75,6 +83,12 @@
private static final int WAITED_HALF = 2;
private static final int OVERDUE = 3;
+ // Track watchdog timeout history and break the crash loop if there is.
+ private static final String TIMEOUT_HISTORY_FILE = "/data/system/watchdog-timeout-history.txt";
+ private static final String PROP_FATAL_LOOP_COUNT = "framework_watchdog.fatal_count";
+ private static final String PROP_FATAL_LOOP_WINDOWS_SECS =
+ "framework_watchdog.fatal_window.second";
+
// Which native processes to dump into dropbox's stack traces
public static final String[] NATIVE_STACKS_OF_INTEREST = new String[] {
"/system/bin/audioserver",
@@ -688,6 +702,10 @@
Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: " + subject);
WatchdogDiagnostics.diagnoseCheckers(blockedCheckers);
Slog.w(TAG, "*** GOODBYE!");
+ if (!Build.IS_USER && isCrashLoopFound()
+ && !WatchdogProperties.is_fatal_ignore().orElse(false)) {
+ breakCrashLoop();
+ }
Process.killProcess(Process.myPid());
System.exit(10);
}
@@ -705,4 +723,107 @@
Slog.w(TAG, "Failed to write to /proc/sysrq-trigger", e);
}
}
+
+ private void resetTimeoutHistory() {
+ writeTimeoutHistory(new ArrayList<String>());
+ }
+
+ private void writeTimeoutHistory(Iterable<String> crashHistory) {
+ String data = String.join(",", crashHistory);
+
+ try (FileWriter writer = new FileWriter(TIMEOUT_HISTORY_FILE)) {
+ writer.write(SystemProperties.get("ro.boottime.zygote"));
+ writer.write(":");
+ writer.write(data);
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to write file " + TIMEOUT_HISTORY_FILE, e);
+ }
+ }
+
+ private String[] readTimeoutHistory() {
+ final String[] emptyStringArray = {};
+
+ try (BufferedReader reader = new BufferedReader(new FileReader(TIMEOUT_HISTORY_FILE))) {
+ String line = reader.readLine();
+ if (line == null) {
+ return emptyStringArray;
+ }
+
+ String[] data = line.trim().split(":");
+ String boottime = data.length >= 1 ? data[0] : "";
+ String history = data.length >= 2 ? data[1] : "";
+ if (SystemProperties.get("ro.boottime.zygote").equals(boottime) && !history.isEmpty()) {
+ return history.split(",");
+ } else {
+ return emptyStringArray;
+ }
+ } catch (FileNotFoundException e) {
+ return emptyStringArray;
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to read file " + TIMEOUT_HISTORY_FILE, e);
+ return emptyStringArray;
+ }
+ }
+
+ private boolean hasActiveUsbConnection() {
+ try {
+ final String state = FileUtils.readTextFile(
+ new File("/sys/class/android_usb/android0/state"),
+ 128 /*max*/, null /*ellipsis*/).trim();
+ if ("CONFIGURED".equals(state)) {
+ return true;
+ }
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to determine if device was on USB", e);
+ }
+ return false;
+ }
+
+ private boolean isCrashLoopFound() {
+ int fatalCount = WatchdogProperties.fatal_count().orElse(0);
+ long fatalWindowMs = TimeUnit.SECONDS.toMillis(
+ WatchdogProperties.fatal_window_second().orElse(0));
+ if (fatalCount == 0 || fatalWindowMs == 0) {
+ if (fatalCount != fatalWindowMs) {
+ Slog.w(TAG, String.format("sysprops '%s' and '%s' should be set or unset together",
+ PROP_FATAL_LOOP_COUNT, PROP_FATAL_LOOP_WINDOWS_SECS));
+ }
+ return false;
+ }
+
+ // new-history = [last (fatalCount - 1) items in old-history] + [nowMs].
+ long nowMs = SystemClock.elapsedRealtime(); // Time since boot including deep sleep.
+ String[] rawCrashHistory = readTimeoutHistory();
+ ArrayList<String> crashHistory = new ArrayList<String>(Arrays.asList(Arrays.copyOfRange(
+ rawCrashHistory,
+ Math.max(0, rawCrashHistory.length - fatalCount - 1),
+ rawCrashHistory.length)));
+ // Something wrong here.
+ crashHistory.add(String.valueOf(nowMs));
+ writeTimeoutHistory(crashHistory);
+
+ // Returns false if the device has an active USB connection.
+ if (hasActiveUsbConnection()) {
+ return false;
+ }
+
+ long firstCrashMs;
+ try {
+ firstCrashMs = Long.parseLong(crashHistory.get(0));
+ } catch (NumberFormatException t) {
+ Slog.w(TAG, "Failed to parseLong " + crashHistory.get(0), t);
+ resetTimeoutHistory();
+ return false;
+ }
+ return crashHistory.size() >= fatalCount && nowMs - firstCrashMs < fatalWindowMs;
+ }
+
+ private void breakCrashLoop() {
+ try (FileWriter kmsg = new FileWriter("/dev/kmsg_debug", /* append= */ true)) {
+ kmsg.append("Fatal reset to escape the system_server crashing loop\n");
+ } catch (IOException e) {
+ Slog.w(TAG, "Failed to append to kmsg", e);
+ }
+ doSysRq('c');
+ }
}
diff --git a/services/core/java/com/android/server/net/NetworkStatsAccess.java b/services/core/java/com/android/server/net/NetworkStatsAccess.java
index 7c1c1c7..72559b4 100644
--- a/services/core/java/com/android/server/net/NetworkStatsAccess.java
+++ b/services/core/java/com/android/server/net/NetworkStatsAccess.java
@@ -28,6 +28,7 @@
import android.app.admin.DevicePolicyManagerInternal;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.os.Process;
import android.os.UserHandle;
import android.telephony.TelephonyManager;
@@ -113,10 +114,11 @@
TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
boolean isDeviceOwner = dpmi != null && dpmi.isActiveAdminWithPolicy(callingUid,
DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ final int appId = UserHandle.getAppId(callingUid);
if (hasCarrierPrivileges || isDeviceOwner
- || UserHandle.getAppId(callingUid) == android.os.Process.SYSTEM_UID) {
- // Carrier-privileged apps and device owners, and the system can access data usage for
- // all apps on the device.
+ || appId == Process.SYSTEM_UID || appId == Process.NETWORK_STACK_UID) {
+ // Carrier-privileged apps and device owners, and the system (including the
+ // network stack) can access data usage for all apps on the device.
return NetworkStatsAccess.Level.DEVICE;
}
diff --git a/services/net/TEST_MAPPING b/services/net/TEST_MAPPING
new file mode 100644
index 0000000..7025dd1
--- /dev/null
+++ b/services/net/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "imports": [
+ {
+ "path": "frameworks/base/core/java/android/net"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/services/usb/java/com/android/server/usb/MtpNotificationManager.java b/services/usb/java/com/android/server/usb/MtpNotificationManager.java
index 462ee19..39f2f29 100644
--- a/services/usb/java/com/android/server/usb/MtpNotificationManager.java
+++ b/services/usb/java/com/android/server/usb/MtpNotificationManager.java
@@ -64,12 +64,13 @@
private final Context mContext;
private final OnOpenInAppListener mListener;
+ private final Receiver mReceiver;
MtpNotificationManager(Context context, OnOpenInAppListener listener) {
mContext = context;
mListener = listener;
- final Receiver receiver = new Receiver();
- context.registerReceiver(receiver, new IntentFilter(ACTION_OPEN_IN_APPS));
+ mReceiver = new Receiver();
+ context.registerReceiver(mReceiver, new IntentFilter(ACTION_OPEN_IN_APPS));
}
void showNotification(UsbDevice device) {
@@ -154,4 +155,8 @@
static interface OnOpenInAppListener {
void onOpenInApp(UsbDevice device);
}
+
+ public void unregister() {
+ mContext.unregisterReceiver(mReceiver);
+ }
}
diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
index d7b6b5d..26ee03c 100644
--- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
@@ -261,6 +261,15 @@
}
/**
+ * Unregister all broadcast receivers. Must be called explicitly before
+ * object deletion.
+ */
+ public void unregisterReceivers() {
+ mPackageMonitor.unregister();
+ mMtpNotificationManager.unregister();
+ }
+
+ /**
* Remove all defaults and denied packages for a user.
*
* @param userToRemove The user
diff --git a/services/usb/java/com/android/server/usb/UsbSettingsManager.java b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
index 7b677ee..8e53ff4 100644
--- a/services/usb/java/com/android/server/usb/UsbSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbSettingsManager.java
@@ -124,6 +124,7 @@
if (mSettingsByProfileGroup.indexOfKey(userToRemove.getIdentifier()) >= 0) {
// The user to remove is the parent user of the group. The parent is the last user
// that gets removed. All state will be removed with the user
+ mSettingsByProfileGroup.get(userToRemove.getIdentifier()).unregisterReceivers();
mSettingsByProfileGroup.remove(userToRemove.getIdentifier());
} else {
// We cannot find the parent user of the user that is removed, hence try to remove
diff --git a/telecomm/java/android/telecom/CallerInfo.java b/telecomm/java/android/telecom/CallerInfo.java
index fb6f994..aff2f01 100644
--- a/telecomm/java/android/telecom/CallerInfo.java
+++ b/telecomm/java/android/telecom/CallerInfo.java
@@ -405,7 +405,8 @@
// Change the callerInfo number ONLY if it is an emergency number
// or if it is the voicemail number. If it is either, take a
// shortcut and skip the query.
- if (PhoneNumberUtils.isLocalEmergencyNumber(context, number)) {
+ TelephonyManager tm = context.getSystemService(TelephonyManager.class);
+ if (tm.isEmergencyNumber(number)) {
return new CallerInfo().markAsEmergency(context);
} else if (PhoneNumberUtils.isVoiceMailNumber(null, subId, number)) {
return new CallerInfo().markAsVoiceMail(context, subId);
diff --git a/telecomm/java/android/telecom/CallerInfoAsyncQuery.java b/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
index 4a81a8e..a9e1a8f 100644
--- a/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
+++ b/telecomm/java/android/telecom/CallerInfoAsyncQuery.java
@@ -34,6 +34,7 @@
import android.provider.ContactsContract.PhoneLookup;
import android.telephony.PhoneNumberUtils;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import java.util.ArrayList;
@@ -481,7 +482,8 @@
cw.subId = subId;
// check to see if these are recognized numbers, and use shortcuts if we can.
- if (PhoneNumberUtils.isLocalEmergencyNumber(context, number)) {
+ TelephonyManager tm = context.getSystemService(TelephonyManager.class);
+ if (tm.isEmergencyNumber(number)) {
cw.event = EVENT_EMERGENCY_NUMBER;
} else if (PhoneNumberUtils.isVoiceMailNumber(context, subId, number)) {
cw.event = EVENT_VOICEMAIL_NUMBER;
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index af49dc4..a3cc0ab 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -2514,13 +2514,12 @@
/**
* Send an MMS message
*
- * <p class="note"><strong>Note:</strong> This method will never trigger an SMS disambiguation
- * dialog. If this method is called on a device that has multiple active subscriptions, this
- * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
- * default subscription is defined, the subscription ID associated with this message will be
- * INVALID, which will result in the operation being completed on the subscription associated
- * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
- * operation is performed on the correct subscription.
+ * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this
+ * manager on a multi-SIM device, this operation may fail sending the MMS message because no
+ * suitable default subscription could be found. In this case, if {@code sentIntent} is
+ * non-null, then the {@link PendingIntent} will be sent with an error code
+ * {@code RESULT_NO_DEFAULT_SMS_APP}. See {@link #getDefault()} for more information on the
+ * conditions where this operation may fail.
* </p>
*
* @param context application context
@@ -2539,21 +2538,30 @@
}
MmsManager m = (MmsManager) context.getSystemService(Context.MMS_SERVICE);
if (m != null) {
- m.sendMultimediaMessage(getSubscriptionId(), contentUri, locationUrl, configOverrides,
- sentIntent, 0L /* messageId */);
+ resolveSubscriptionForOperation(new SubscriptionResolverResult() {
+ @Override
+ public void onSuccess(int subId) {
+ m.sendMultimediaMessage(subId, contentUri, locationUrl, configOverrides,
+ sentIntent, 0L /* messageId */);
+ }
+
+ @Override
+ public void onFailure() {
+ notifySmsError(sentIntent, RESULT_NO_DEFAULT_SMS_APP);
+ }
+ });
}
}
/**
* Download an MMS message from carrier by a given location URL
*
- * <p class="note"><strong>Note:</strong> This method will never trigger an SMS disambiguation
- * dialog. If this method is called on a device that has multiple active subscriptions, this
- * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
- * default subscription is defined, the subscription ID associated with this message will be
- * INVALID, which will result in the operation being completed on the subscription associated
- * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
- * operation is performed on the correct subscription.
+ * <p class="note"><strong>Note:</strong> If {@link #getDefault()} is used to instantiate this
+ * manager on a multi-SIM device, this operation may fail downloading the MMS message because no
+ * suitable default subscription could be found. In this case, if {@code downloadedIntent} is
+ * non-null, then the {@link PendingIntent} will be sent with an error code
+ * {@code RESULT_NO_DEFAULT_SMS_APP}. See {@link #getDefault()} for more information on the
+ * conditions where this operation may fail.
* </p>
*
* @param context application context
@@ -2576,8 +2584,18 @@
}
MmsManager m = (MmsManager) context.getSystemService(Context.MMS_SERVICE);
if (m != null) {
- m.downloadMultimediaMessage(getSubscriptionId(), locationUrl, contentUri,
- configOverrides, downloadedIntent, 0L /* messageId */);
+ resolveSubscriptionForOperation(new SubscriptionResolverResult() {
+ @Override
+ public void onSuccess(int subId) {
+ m.downloadMultimediaMessage(subId, locationUrl, contentUri, configOverrides,
+ downloadedIntent, 0L /* messageId */);
+ }
+
+ @Override
+ public void onFailure() {
+ notifySmsError(downloadedIntent, RESULT_NO_DEFAULT_SMS_APP);
+ }
+ });
}
}
diff --git a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.aidl b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.aidl
new file mode 100644
index 0000000..a704702
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+
+package android.telephony.ims;
+
+parcelable RcsContactPresenceTuple;
diff --git a/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
new file mode 100644
index 0000000..b0aaa92
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsContactPresenceTuple.java
@@ -0,0 +1,360 @@
+/*
+ * 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.
+ */
+
+package android.telephony.ims;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringDef;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents a PIDF tuple element that is part of the presence element returned from the carrier
+ * network during a SUBSCRIBE request. See RFC3863 for more information.
+ * @hide
+ */
+public class RcsContactPresenceTuple implements Parcelable {
+
+ /** The service id of the MMTEL */
+ public static final String SERVICE_ID_MMTEL = "org.3gpp.urn:urn-7:3gpp-service.ims.icsi.mmtel";
+
+ /** The service capabilities is available. */
+ public static final String TUPLE_BASIC_STATUS_OPEN = "open";
+
+ /** The service capabilities is unavailable. */
+ public static final String TUPLE_BASIC_STATUS_CLOSED = "closed";
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(prefix = "TUPLE_BASIC_STATUS_", value = {
+ TUPLE_BASIC_STATUS_OPEN,
+ TUPLE_BASIC_STATUS_CLOSED
+ })
+ public @interface BasicStatus {}
+
+ /**
+ * An optional addition to the PIDF Presence Tuple containing service capabilities, which is
+ * defined in the servcaps element. See RFC5196, section 3.2.1.
+ */
+ public static class ServiceCapabilities implements Parcelable {
+
+ /** The service can simultaneously send and receive data. */
+ public static final String DUPLEX_MODE_FULL = "full";
+
+ /** The service can alternate between sending and receiving data.*/
+ public static final String DUPLEX_MODE_HALF = "half";
+
+ /** The service can only receive data. */
+ public static final String DUPLEX_MODE_RECEIVE_ONLY = "receive-only";
+
+ /** The service can only send data. */
+ public static final String DUPLEX_MODE_SEND_ONLY = "send-only";
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @StringDef(prefix = "DUPLEX_MODE_", value = {
+ DUPLEX_MODE_FULL,
+ DUPLEX_MODE_HALF,
+ DUPLEX_MODE_RECEIVE_ONLY,
+ DUPLEX_MODE_SEND_ONLY
+ })
+ public @interface DuplexMode {}
+
+ /**
+ * Builder to help construct {@link ServiceCapabilities} instances.
+ */
+ public static class Builder {
+
+ private ServiceCapabilities mCapabilities;
+
+ /**
+ * Create the ServiceCapabilities builder, which can be used to set service capabilities
+ * as well as custom capability extensions.
+ * @param isAudioCapable Whether the audio is capable or not.
+ * @param isVideoCapable Whether the video is capable or not.
+ */
+ public Builder(boolean isAudioCapable, boolean isVideoCapable) {
+ mCapabilities = new ServiceCapabilities(isAudioCapable, isVideoCapable);
+ }
+
+ /**
+ * Add the supported duplex mode.
+ * @param mode The supported duplex mode
+ */
+ public Builder addSupportedDuplexMode(@NonNull @DuplexMode String mode) {
+ mCapabilities.mSupportedDuplexModeList.add(mode);
+ return this;
+ }
+
+ /**
+ * Add the unsupported duplex mode.
+ * @param mode The unsupported duplex mode
+ */
+ public Builder addUnsupportedDuplexMode(@NonNull @DuplexMode String mode) {
+ mCapabilities.mUnsupportedDuplexModeList.add(mode);
+ return this;
+ }
+
+ /**
+ * @return the ServiceCapabilities instance.
+ */
+ public ServiceCapabilities build() {
+ return mCapabilities;
+ }
+ }
+
+ private final boolean mIsAudioCapable;
+ private final boolean mIsVideoCapable;
+ private final @DuplexMode List<String> mSupportedDuplexModeList = new ArrayList<>();
+ private final @DuplexMode List<String> mUnsupportedDuplexModeList = new ArrayList<>();
+
+ /**
+ * Use {@link Builder} to build an instance of this interface.
+ * @param isAudioCapable Whether the audio is capable.
+ * @param isVideoCapable Whether the video is capable.
+ */
+ ServiceCapabilities(boolean isAudioCapable, boolean isVideoCapable) {
+ mIsAudioCapable = isAudioCapable;
+ mIsVideoCapable = isVideoCapable;
+ }
+
+ private ServiceCapabilities(Parcel in) {
+ mIsAudioCapable = in.readBoolean();
+ mIsVideoCapable = in.readBoolean();
+ in.readStringList(mSupportedDuplexModeList);
+ in.readStringList(mUnsupportedDuplexModeList);
+ }
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeBoolean(mIsAudioCapable);
+ out.writeBoolean(mIsVideoCapable);
+ out.writeStringList(mSupportedDuplexModeList);
+ out.writeStringList(mUnsupportedDuplexModeList);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Creator<ServiceCapabilities> CREATOR =
+ new Creator<ServiceCapabilities>() {
+ @Override
+ public ServiceCapabilities createFromParcel(Parcel in) {
+ return new ServiceCapabilities(in);
+ }
+
+ @Override
+ public ServiceCapabilities[] newArray(int size) {
+ return new ServiceCapabilities[size];
+ }
+ };
+
+ /**
+ * Query the audio capable.
+ * @return true if the audio is capable, false otherwise.
+ */
+ public boolean isAudioCapable() {
+ return mIsAudioCapable;
+ }
+
+ /**
+ * Query the video capable.
+ * @return true if the video is capable, false otherwise.
+ */
+ public boolean isVideoCapable() {
+ return mIsVideoCapable;
+ }
+
+ /**
+ * Get the supported duplex mode list.
+ * @return The list of supported duplex mode
+ */
+ public @NonNull @DuplexMode List<String> getSupportedDuplexModes() {
+ return Collections.unmodifiableList(mSupportedDuplexModeList);
+ }
+
+ /**
+ * Get the unsupported duplex mode list.
+ * @return The list of unsupported duplex mode
+ */
+ public @NonNull @DuplexMode List<String> getUnsupportedDuplexModes() {
+ return Collections.unmodifiableList(mUnsupportedDuplexModeList);
+ }
+ }
+
+ /**
+ * Builder to help construct {@link RcsContactPresenceTuple} instances.
+ */
+ public static class Builder {
+
+ private RcsContactPresenceTuple mPresenceTuple;
+
+ /**
+ * Builds a RcsContactPresenceTuple instance.
+ * @param serviceId The OMA Presence service-id associated with this capability. See the
+ * OMA Presence SIMPLE specification v1.1, section 10.5.1.
+ * @param serviceVersion The OMA Presence version associated with the service capability.
+ * See the OMA Presence SIMPLE specification v1.1, section 10.5.1.
+ */
+ public Builder(@NonNull @BasicStatus String status, @NonNull String serviceId,
+ @NonNull String serviceVersion) {
+ mPresenceTuple = new RcsContactPresenceTuple(status, serviceId, serviceVersion);
+ }
+
+ /**
+ * The optional SIP Contact URI associated with the PIDF tuple element.
+ */
+ public Builder addContactUri(@NonNull Uri contactUri) {
+ mPresenceTuple.mContactUri = contactUri;
+ return this;
+ }
+
+ /**
+ * The optional timestamp indicating the data and time of the status change of this tuple.
+ * See RFC3863, section 4.1.7 for more information on the expected format.
+ */
+ public Builder addTimeStamp(@NonNull String timestamp) {
+ mPresenceTuple.mTimestamp = timestamp;
+ return this;
+ }
+
+ /**
+ * An optional parameter containing the description element of the service-description. See
+ * OMA Presence SIMPLE specification v1.1
+ */
+ public Builder addDescription(@NonNull String description) {
+ mPresenceTuple.mServiceDescription = description;
+ return this;
+ }
+
+ /**
+ * An optional parameter containing the service capabilities of the presence tuple if they
+ * are present in the servcaps element.
+ */
+ public Builder addServiceCapabilities(@NonNull ServiceCapabilities caps) {
+ mPresenceTuple.mServiceCapabilities = caps;
+ return this;
+ }
+
+ /**
+ * @return the constructed instance.
+ */
+ public RcsContactPresenceTuple build() {
+ return mPresenceTuple;
+ }
+ }
+
+ private Uri mContactUri;
+ private String mTimestamp;
+ private @BasicStatus String mStatus;
+
+ // The service information in the service-description element.
+ private String mServiceId;
+ private String mServiceVersion;
+ private String mServiceDescription;
+
+ private ServiceCapabilities mServiceCapabilities;
+
+ private RcsContactPresenceTuple(@NonNull @BasicStatus String status, @NonNull String serviceId,
+ @NonNull String serviceVersion) {
+ mStatus = status;
+ mServiceId = serviceId;
+ mServiceVersion = serviceVersion;
+ }
+
+ private RcsContactPresenceTuple(Parcel in) {
+ mContactUri = in.readParcelable(Uri.class.getClassLoader());
+ mTimestamp = in.readString();
+ mStatus = in.readString();
+ mServiceId = in.readString();
+ mServiceVersion = in.readString();
+ mServiceDescription = in.readString();
+ mServiceCapabilities = in.readParcelable(ServiceCapabilities.class.getClassLoader());
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeParcelable(mContactUri, flags);
+ out.writeString(mTimestamp);
+ out.writeString(mStatus);
+ out.writeString(mServiceId);
+ out.writeString(mServiceVersion);
+ out.writeString(mServiceDescription);
+ out.writeParcelable(mServiceCapabilities, flags);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Creator<RcsContactPresenceTuple> CREATOR =
+ new Creator<RcsContactPresenceTuple>() {
+ @Override
+ public RcsContactPresenceTuple createFromParcel(Parcel in) {
+ return new RcsContactPresenceTuple(in);
+ }
+
+ @Override
+ public RcsContactPresenceTuple[] newArray(int size) {
+ return new RcsContactPresenceTuple[size];
+ }
+ };
+
+ /** @return the status of the tuple element. */
+ public @NonNull @BasicStatus String getStatus() {
+ return mStatus;
+ }
+
+ /** @return the service-id element of the service-description */
+ public @NonNull String getServiceId() {
+ return mServiceId;
+ }
+
+ /** @return the version element of the service-description */
+ public @NonNull String getServiceVersion() {
+ return mServiceVersion;
+ }
+
+ /** @return the SIP URI contained in the contact element of the tuple if it exists. */
+ public @Nullable Uri getContactUri() {
+ return mContactUri;
+ }
+
+ /** @return the timestamp element contained in the tuple if it exists */
+ public @Nullable String getTimestamp() {
+ return mTimestamp;
+ }
+
+ /** @return the description element contained in the service-description if it exists */
+ public @Nullable String getServiceDescription() {
+ return mServiceDescription;
+ }
+
+ /** @return the {@link ServiceCapabilities} of the tuple if it exists. */
+ public @Nullable ServiceCapabilities getServiceCapabilities() {
+ return mServiceCapabilities;
+ }
+}
diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
index dc36edf..d12a6ae 100644
--- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
@@ -16,7 +16,7 @@
package android.telephony.ims;
-import android.annotation.LongDef;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.net.Uri;
@@ -27,9 +27,7 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
-import java.util.Map;
/**
* Contains the User Capability Exchange capabilities corresponding to a contact's URI.
@@ -37,114 +35,80 @@
*/
public final class RcsContactUceCapability implements Parcelable {
- /** Supports 1-to-1 chat */
- public static final int CAPABILITY_CHAT_STANDALONE = (1 << 0);
- /** Supports group chat */
- public static final int CAPABILITY_CHAT_SESSION = (1 << 1);
- /** Supports full store and forward group chat information. */
- public static final int CAPABILITY_CHAT_SESSION_STORE_FORWARD = (1 << 2);
- /**
- * Supports file transfer via Message Session Relay Protocol (MSRP) without Store and Forward.
- */
- public static final int CAPABILITY_FILE_TRANSFER = (1 << 3);
- /** Supports File Transfer Thumbnail */
- public static final int CAPABILITY_FILE_TRANSFER_THUMBNAIL = (1 << 4);
- /** Supports File Transfer with Store and Forward */
- public static final int CAPABILITY_FILE_TRANSFER_STORE_FORWARD = (1 << 5);
- /** Supports File Transfer via HTTP */
- public static final int CAPABILITY_FILE_TRANSFER_HTTP = (1 << 6);
- /** Supports file transfer via SMS */
- public static final int CAPABILITY_FILE_TRANSFER_SMS = (1 << 7);
- /** Supports image sharing */
- public static final int CAPABILITY_IMAGE_SHARE = (1 << 8);
- /** Supports video sharing during a circuit-switch call (IR.74)*/
- public static final int CAPABILITY_VIDEO_SHARE_DURING_CS_CALL = (1 << 9);
- /** Supports video share outside of voice call (IR.84) */
- public static final int CAPABILITY_VIDEO_SHARE = (1 << 10);
- /** Supports social presence information */
- public static final int CAPABILITY_SOCIAL_PRESENCE = (1 << 11);
- /** Supports capability discovery via presence */
- public static final int CAPABILITY_DISCOVERY_VIA_PRESENCE = (1 << 12);
- /** Supports IP Voice calling over LTE or IWLAN (IR.92/IR.51) */
- public static final int CAPABILITY_IP_VOICE_CALL = (1 << 13);
- /** Supports IP video calling (IR.94) */
- public static final int CAPABILITY_IP_VIDEO_CALL = (1 << 14);
- /** Supports Geolocation PUSH during 1-to-1 or multiparty chat */
- public static final int CAPABILITY_GEOLOCATION_PUSH = (1 << 15);
- /** Supports Geolocation PUSH via SMS for fallback. */
- public static final int CAPABILITY_GEOLOCATION_PUSH_SMS = (1 << 16);
- /** Supports Geolocation pull. */
- public static final int CAPABILITY_GEOLOCATION_PULL = (1 << 17);
- /** Supports Geolocation pull using file transfer support. */
- public static final int CAPABILITY_GEOLOCATION_PULL_FILE_TRANSFER = (1 << 18);
- /** Supports RCS voice calling */
- public static final int CAPABILITY_RCS_VOICE_CALL = (1 << 19);
- /** Supports RCS video calling */
- public static final int CAPABILITY_RCS_VIDEO_CALL = (1 << 20);
- /** Supports RCS video calling, where video media can not be dropped. */
- public static final int CAPABILITY_RCS_VIDEO_ONLY_CALL = (1 << 21);
- /** Supports call composer, where outgoing calls can be enriched with pre-call content.*/
- public static final int CAPABILITY_CALL_COMPOSER = (1 << 22);
- /** Supports post call information that is included in the call if the call is missed.*/
- public static final int CAPABILITY_POST_CALL = (1 << 23);
- /** Supports sharing a map where the user can draw, share markers, and share their position. */
- public static final int CAPABILITY_SHARED_MAP = (1 << 24);
- /** Supports sharing a canvas, where users can draw, add images, and change background colors.*/
- public static final int CAPABILITY_SHARED_SKETCH = (1 << 25);
- /** Supports communication with Chatbots. */
- public static final int CAPABILITY_CHAT_BOT = (1 << 26);
- /** Supports Chatbot roles. */
- public static final int CAPABILITY_CHAT_BOT_ROLE = (1 << 27);
- /** Supports the unidirectional plug-ins framework. */
- public static final int CAPABILITY_PLUG_IN = (1 << 28);
- /** Supports standalone Chatbot communication. */
- public static final int CAPABILITY_STANDALONE_CHAT_BOT = (1 << 29);
- /** Supports MMTEL based call composer. */
- public static final int CAPABILITY_MMTEL_CALL_COMPOSER = (1 << 30);
+ /** Contains presence information associated with the contact */
+ public static final int CAPABILITY_MECHANISM_PRESENCE = 1;
+ /** Contains OPTIONS information associated with the contact */
+ public static final int CAPABILITY_MECHANISM_OPTIONS = 2;
-
- /** @hide*/
+ /** @hide */
@Retention(RetentionPolicy.SOURCE)
- @LongDef(prefix = "CAPABILITY_", flag = true, value = {
- CAPABILITY_CHAT_STANDALONE,
- CAPABILITY_CHAT_SESSION,
- CAPABILITY_CHAT_SESSION_STORE_FORWARD,
- CAPABILITY_FILE_TRANSFER,
- CAPABILITY_FILE_TRANSFER_THUMBNAIL,
- CAPABILITY_FILE_TRANSFER_STORE_FORWARD,
- CAPABILITY_FILE_TRANSFER_HTTP,
- CAPABILITY_FILE_TRANSFER_SMS,
- CAPABILITY_IMAGE_SHARE,
- CAPABILITY_VIDEO_SHARE_DURING_CS_CALL,
- CAPABILITY_VIDEO_SHARE,
- CAPABILITY_SOCIAL_PRESENCE,
- CAPABILITY_DISCOVERY_VIA_PRESENCE,
- CAPABILITY_IP_VOICE_CALL,
- CAPABILITY_IP_VIDEO_CALL,
- CAPABILITY_GEOLOCATION_PUSH,
- CAPABILITY_GEOLOCATION_PUSH_SMS,
- CAPABILITY_GEOLOCATION_PULL,
- CAPABILITY_GEOLOCATION_PULL_FILE_TRANSFER,
- CAPABILITY_RCS_VOICE_CALL,
- CAPABILITY_RCS_VIDEO_CALL,
- CAPABILITY_RCS_VIDEO_ONLY_CALL,
- CAPABILITY_CALL_COMPOSER,
- CAPABILITY_POST_CALL,
- CAPABILITY_SHARED_MAP,
- CAPABILITY_SHARED_SKETCH,
- CAPABILITY_CHAT_BOT,
- CAPABILITY_CHAT_BOT_ROLE,
- CAPABILITY_PLUG_IN,
- CAPABILITY_STANDALONE_CHAT_BOT,
- CAPABILITY_MMTEL_CALL_COMPOSER
+ @IntDef(prefix = "CAPABILITY_MECHANISM_", value = {
+ CAPABILITY_MECHANISM_PRESENCE,
+ CAPABILITY_MECHANISM_OPTIONS
})
- public @interface CapabilityFlag {}
+ public @interface CapabilityMechanism {}
/**
- * Builder to help construct {@link RcsContactUceCapability} instances.
+ * The capabilities of this contact were requested recently enough to still be considered in
+ * the availability window.
*/
- public static class Builder {
+ public static final int SOURCE_TYPE_NETWORK = 0;
+
+ /**
+ * The capabilities of this contact were retrieved from the cached information in the Enhanced
+ * Address Book.
+ */
+ public static final int SOURCE_TYPE_CACHED = 1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "SOURCE_TYPE_", value = {
+ SOURCE_TYPE_NETWORK,
+ SOURCE_TYPE_CACHED
+ })
+ public @interface SourceType {}
+
+ /**
+ * The requested contact was found to be offline when queried. This is only applicable to
+ * contact capabilities that were queried via OPTIONS requests and the network returned a
+ * 408/480 response.
+ */
+ public static final int REQUEST_RESULT_NOT_ONLINE = 0;
+
+ /**
+ * Capability information for the requested contact was not found. The contact should not be
+ * considered an RCS user.
+ */
+ public static final int REQUEST_RESULT_NOT_FOUND = 1;
+
+ /**
+ * Capability information for the requested contact was found successfully.
+ */
+ public static final int REQUEST_RESULT_FOUND = 2;
+
+ /**
+ * Capability information for the requested contact has expired and can not be refreshed due to
+ * a temporary network error. This is a temporary error and the capabilities of the contact
+ * should be queried again at a later time.
+ */
+ public static final int REQUEST_RESULT_UNKNOWN = 3;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "REQUEST_RESULT_", value = {
+ REQUEST_RESULT_NOT_ONLINE,
+ REQUEST_RESULT_NOT_FOUND,
+ REQUEST_RESULT_FOUND,
+ REQUEST_RESULT_UNKNOWN
+ })
+ public @interface RequestResult {}
+
+ /**
+ * Builder to help construct {@link RcsContactUceCapability} instances when capabilities were
+ * queried through SIP OPTIONS.
+ */
+ public static class OptionsBuilder {
private final RcsContactUceCapability mCapabilities;
@@ -153,51 +117,38 @@
* capability extensions.
* @param contact The contact URI that the capabilities are attached to.
*/
- public Builder(@NonNull Uri contact) {
- mCapabilities = new RcsContactUceCapability(contact);
+ public OptionsBuilder(@NonNull Uri contact) {
+ mCapabilities = new RcsContactUceCapability(contact, CAPABILITY_MECHANISM_OPTIONS,
+ SOURCE_TYPE_NETWORK);
}
/**
- * Add a UCE capability bit-field as well as the associated URI that the framework should
- * use for those services. This is mainly used for capabilities that may use a URI separate
- * from the contact's URI, for example the URI to use for VT calls.
- * @param type The capability to map to a service URI that is different from the contact's
- * URI.
+ * Set the result of the capabilities request.
+ * @param requestResult the request result
+ * @return this OptionBuilder
*/
- public @NonNull Builder add(@CapabilityFlag long type, @NonNull Uri serviceUri) {
- mCapabilities.mCapabilities |= type;
- // Put each of these capabilities into the map separately.
- for (long shift = 0; shift < Integer.SIZE; shift++) {
- long cap = type & (1 << shift);
- if (cap != 0) {
- mCapabilities.mServiceMap.put(cap, serviceUri);
- // remove that capability from the field.
- type &= ~cap;
- }
- if (type == 0) {
- // no need to keep going, end early.
- break;
- }
- }
+ public @NonNull OptionsBuilder setRequestResult(@RequestResult int requestResult) {
+ mCapabilities.mRequestResult = requestResult;
return this;
}
/**
- * Add a UCE capability flag that this contact supports.
- * @param type the capability that the contact supports.
+ * Add the feature tag into the capabilities instance.
+ * @param tag the supported feature tag
+ * @return this OptionBuilder
*/
- public @NonNull Builder add(@CapabilityFlag long type) {
- mCapabilities.mCapabilities |= type;
+ public @NonNull OptionsBuilder addFeatureTag(String tag) {
+ mCapabilities.mFeatureTags.add(tag);
return this;
}
/**
- * Add a carrier specific service tag.
- * @param extension A string containing a carrier specific service tag that is an extension
- * of the {@link CapabilityFlag}s that are defined here.
+ * Add the list of feature tag into the capabilities instance.
+ * @param tags the list of the supported feature tags
+ * @return this OptionBuilder
*/
- public @NonNull Builder add(@NonNull String extension) {
- mCapabilities.mExtensionTags.add(extension);
+ public @NonNull OptionsBuilder addFeatureTags(List<String> tags) {
+ mCapabilities.mFeatureTags.addAll(tags);
return this;
}
@@ -209,56 +160,88 @@
}
}
- private final Uri mContactUri;
- private long mCapabilities;
- private List<String> mExtensionTags = new ArrayList<>();
- private Map<Long, Uri> mServiceMap = new HashMap<>();
-
/**
- * Use {@link Builder} to build an instance of this interface.
- * @param contact The URI associated with this capability information.
- * @hide
+ * Builder to help construct {@link RcsContactUceCapability} instances when capabilities were
+ * queried through a presence server.
*/
- RcsContactUceCapability(@NonNull Uri contact) {
- mContactUri = contact;
+ public static class PresenceBuilder {
+
+ private final RcsContactUceCapability mCapabilities;
+
+ /**
+ * Create the builder, which can be used to set UCE capabilities as well as custom
+ * capability extensions.
+ * @param contact The contact URI that the capabilities are attached to.
+ * @param sourceType The type where the capabilities of this contact were retrieved from.
+ * @param requestResult the request result
+ */
+ public PresenceBuilder(@NonNull Uri contact, @SourceType int sourceType,
+ @RequestResult int requestResult) {
+ mCapabilities = new RcsContactUceCapability(contact, CAPABILITY_MECHANISM_PRESENCE,
+ sourceType);
+ mCapabilities.mRequestResult = requestResult;
+ }
+
+ /**
+ * Add the {@link RcsContactPresenceTuple} into the capabilities instance.
+ * @param tuple The {@link RcsContactPresenceTuple} to be added into.
+ * @return this PresenceBuilder
+ */
+ public @NonNull PresenceBuilder addCapabilityTuple(RcsContactPresenceTuple tuple) {
+ mCapabilities.mPresenceTuples.add(tuple);
+ return this;
+ }
+
+ /**
+ * Add the list of {@link RcsContactPresenceTuple} into the capabilities instance.
+ * @param tuples The list of the {@link RcsContactPresenceTuple} to be added into.
+ * @return this PresenceBuilder
+ */
+ public @NonNull PresenceBuilder addCapabilityTuples(List<RcsContactPresenceTuple> tuples) {
+ mCapabilities.mPresenceTuples.addAll(tuples);
+ return this;
+ }
+
+ /**
+ * @return the RcsContactUceCapability instance.
+ */
+ public @NonNull RcsContactUceCapability build() {
+ return mCapabilities;
+ }
+ }
+
+ private final Uri mContactUri;
+ private @SourceType int mSourceType;
+ private @CapabilityMechanism int mCapabilityMechanism;
+ private @RequestResult int mRequestResult;
+
+ private final List<String> mFeatureTags = new ArrayList<>();
+ private final List<RcsContactPresenceTuple> mPresenceTuples = new ArrayList<>();
+
+ private RcsContactUceCapability(@NonNull Uri contactUri, @CapabilityMechanism int mechanism,
+ @SourceType int sourceType) {
+ mContactUri = contactUri;
+ mCapabilityMechanism = mechanism;
+ mSourceType = sourceType;
}
private RcsContactUceCapability(Parcel in) {
mContactUri = in.readParcelable(Uri.class.getClassLoader());
- mCapabilities = in.readLong();
- in.readStringList(mExtensionTags);
- // read mServiceMap as key,value pair
- int mapSize = in.readInt();
- for (int i = 0; i < mapSize; i++) {
- mServiceMap.put(in.readLong(), in.readParcelable(Uri.class.getClassLoader()));
- }
+ mCapabilityMechanism = in.readInt();
+ mSourceType = in.readInt();
+ mRequestResult = in.readInt();
+ in.readStringList(mFeatureTags);
+ in.readParcelableList(mPresenceTuples, RcsContactPresenceTuple.class.getClassLoader());
}
- public static final @NonNull Creator<RcsContactUceCapability> CREATOR =
- new Creator<RcsContactUceCapability>() {
- @Override
- public RcsContactUceCapability createFromParcel(Parcel in) {
- return new RcsContactUceCapability(in);
- }
-
- @Override
- public RcsContactUceCapability[] newArray(int size) {
- return new RcsContactUceCapability[size];
- }
- };
-
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
- out.writeParcelable(mContactUri, 0);
- out.writeLong(mCapabilities);
- out.writeStringList(mExtensionTags);
- // write mServiceMap as key,value pairs
- int mapSize = mServiceMap.keySet().size();
- out.writeInt(mapSize);
- for (long key : mServiceMap.keySet()) {
- out.writeLong(key);
- out.writeParcelable(mServiceMap.get(key), 0);
- }
+ out.writeParcelable(mContactUri, flags);
+ out.writeInt(mCapabilityMechanism);
+ out.writeInt(mSourceType);
+ out.writeInt(mRequestResult);
+ out.writeStringList(mFeatureTags);
+ out.writeParcelableList(mPresenceTuples, flags);
}
@Override
@@ -266,49 +249,87 @@
return 0;
}
+ public static final @NonNull Creator<RcsContactUceCapability> CREATOR =
+ new Creator<RcsContactUceCapability>() {
+ @Override
+ public RcsContactUceCapability createFromParcel(Parcel in) {
+ return new RcsContactUceCapability(in);
+ }
+
+ @Override
+ public RcsContactUceCapability[] newArray(int size) {
+ return new RcsContactUceCapability[size];
+ }
+ };
+
/**
- * Query for a capability
- * @param type The capability flag to query.
- * @return true if the capability flag specified is set, false otherwise.
+ * @return The mechanism used to get the capabilities.
*/
- public boolean isCapable(@CapabilityFlag long type) {
- return (mCapabilities & type) > 0;
+ public @CapabilityMechanism int getCapabilityMechanism() {
+ return mCapabilityMechanism;
}
/**
- * @return true if the extension service tag is set, false otherwise.
- */
- public boolean isCapable(@NonNull String extensionTag) {
- return mExtensionTags.contains(extensionTag);
- }
-
- /**
- * @return An immutable list containing all of the extension tags that have been set as capable.
- * @throws UnsupportedOperationException if this list is modified.
- */
- public @NonNull List<String> getCapableExtensionTags() {
- return Collections.unmodifiableList(mExtensionTags);
- }
-
- /**
- * Retrieves the {@link Uri} associated with the capability being queried.
+ * @return The feature tags present in the OPTIONS response from the network.
* <p>
- * This will typically be the contact {@link Uri} available via {@link #getContactUri()} unless
- * a different service {@link Uri} was associated with this capability using
- * {@link Builder#add(long, Uri)}.
- *
- * @return a String containing the {@link Uri} associated with the service tag or
- * {@code null} if this capability is not set as capable.
- * @see #isCapable(long)
+ * Note: this is only populated if {@link #getCapabilityMechanism} is
+ * {@link CAPABILITY_MECHANISM_OPTIONS}
*/
- public @Nullable Uri getServiceUri(@CapabilityFlag long type) {
- Uri result = mServiceMap.getOrDefault(type, null);
- // If the capability is capable, but does not have a service URI associated, use the default
- // contact URI.
- if (result == null) {
- return isCapable(type) ? getContactUri() : null;
+ public @NonNull List<String> getOptionsFeatureTags() {
+ if (mCapabilityMechanism != CAPABILITY_MECHANISM_OPTIONS) {
+ return Collections.emptyList();
}
- return result;
+ return Collections.unmodifiableList(mFeatureTags);
+ }
+
+ /**
+ * @return The tuple elements associated with the presence element portion of the PIDF document
+ * contained in the NOTIFY response from the network.
+ * <p>
+ * Note: this is only populated if {@link #getCapabilityMechanism} is
+ * {@link CAPABILITY_MECHANISM_PRESENCE}
+ */
+ public @NonNull List<RcsContactPresenceTuple> getPresenceTuples() {
+ if (mCapabilityMechanism != CAPABILITY_MECHANISM_PRESENCE) {
+ return Collections.emptyList();
+ }
+ return Collections.unmodifiableList(mPresenceTuples);
+ }
+
+ /**
+ * Get the RcsContactPresenceTuple associated with the given service id.
+ * @param serviceId The service id to get the presence tuple.
+ * @return The RcsContactPresenceTuple which has the given service id.
+ *
+ * <p>
+ * Note: this is only populated if {@link #getCapabilityMechanism} is
+ * {@link CAPABILITY_MECHANISM_PRESENCE}
+ */
+ public @Nullable RcsContactPresenceTuple getPresenceTuple(String serviceId) {
+ if (mCapabilityMechanism != CAPABILITY_MECHANISM_PRESENCE) {
+ return null;
+ }
+ for (RcsContactPresenceTuple tuple : mPresenceTuples) {
+ if (tuple.getServiceId().equals(serviceId)) {
+ return tuple;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @return the source of the data that was used to populate the capabilities of the requested
+ * contact.
+ */
+ public @SourceType int getSourceType() {
+ return mSourceType;
+ }
+
+ /**
+ * @return the result of querying the capabilities of the requested contact.
+ */
+ public @RequestResult int getRequestResult() {
+ return mRequestResult;
}
/**
diff --git a/tests/net/TEST_MAPPING b/tests/net/TEST_MAPPING
index 005cbe9..89fc6ea 100644
--- a/tests/net/TEST_MAPPING
+++ b/tests/net/TEST_MAPPING
@@ -8,5 +8,10 @@
{
"name": "FrameworksNetDeflakeTest"
}
+ ],
+ "imports": [
+ {
+ "path": "cts/tests/tests/net"
+ }
]
}
\ No newline at end of file