Merge "Change MetadataListener to a abstract static class"
diff --git a/api/current.txt b/api/current.txt
index 35cedf3..37dffa8 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -8385,6 +8385,13 @@
method @Deprecated @BinderThread public void onHealthChannelStateChange(android.bluetooth.BluetoothHealthAppConfiguration, android.bluetooth.BluetoothDevice, int, int, android.os.ParcelFileDescriptor, int);
}
+ public final class BluetoothHearingAid implements android.bluetooth.BluetoothProfile {
+ method public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method public int getConnectionState(android.bluetooth.BluetoothDevice);
+ method public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
+ field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED";
+ }
+
public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile {
method public boolean connect(android.bluetooth.BluetoothDevice);
method public boolean disconnect(android.bluetooth.BluetoothDevice);
@@ -8480,6 +8487,7 @@
field public static final int GATT_SERVER = 8; // 0x8
field public static final int HEADSET = 1; // 0x1
field @Deprecated public static final int HEALTH = 3; // 0x3
+ field public static final int HEARING_AID = 21; // 0x15
field public static final int HID_DEVICE = 19; // 0x13
field public static final int SAP = 10; // 0xa
field public static final int STATE_CONNECTED = 2; // 0x2
@@ -11356,6 +11364,7 @@
field public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
field public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc";
field public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
+ field public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims";
field public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms";
field @Deprecated public static final String FEATURE_TELEVISION = "android.hardware.type.television";
field public static final String FEATURE_TOUCHSCREEN = "android.hardware.touchscreen";
@@ -27103,6 +27112,7 @@
public class ConnectivityManager {
method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
method public boolean bindProcessToNetwork(@Nullable android.net.Network);
+ method public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull android.net.IpSecManager.UdpEncapsulationSocket, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
method @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) @Nullable public android.net.Network getActiveNetwork();
method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo getActiveNetworkInfo();
method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE) public android.net.NetworkInfo[] getAllNetworkInfo();
@@ -27607,6 +27617,29 @@
ctor public SSLSessionCache(android.content.Context);
}
+ public abstract class SocketKeepalive implements java.lang.AutoCloseable {
+ method public final void close();
+ method public final void start(@IntRange(from=0xa, to=0xe10) int);
+ method public final void stop();
+ field public static final int ERROR_HARDWARE_ERROR = -31; // 0xffffffe1
+ field public static final int ERROR_HARDWARE_UNSUPPORTED = -30; // 0xffffffe2
+ field public static final int ERROR_INVALID_INTERVAL = -24; // 0xffffffe8
+ field public static final int ERROR_INVALID_IP_ADDRESS = -21; // 0xffffffeb
+ field public static final int ERROR_INVALID_LENGTH = -23; // 0xffffffe9
+ field public static final int ERROR_INVALID_NETWORK = -20; // 0xffffffec
+ field public static final int ERROR_INVALID_PORT = -22; // 0xffffffea
+ field public static final int ERROR_INVALID_SOCKET = -25; // 0xffffffe7
+ field public static final int ERROR_SOCKET_NOT_IDLE = -26; // 0xffffffe6
+ }
+
+ public static class SocketKeepalive.Callback {
+ ctor public SocketKeepalive.Callback();
+ method public void onDataReceived();
+ method public void onError(int);
+ method public void onStarted();
+ method public void onStopped();
+ }
+
public class TrafficStats {
ctor public TrafficStats();
method public static void clearThreadStatsTag();
@@ -37360,6 +37393,7 @@
field public static final String CONTENT_ID = "cid";
field public static final String CONTENT_LOCATION = "cl";
field public static final String CONTENT_TYPE = "ct";
+ field public static final android.net.Uri CONTENT_URI;
field public static final String CT_START = "ctt_s";
field public static final String CT_TYPE = "ctt_t";
field public static final String FILENAME = "fn";
@@ -42854,6 +42888,7 @@
method public String getNumber();
method public int getSimSlotIndex();
method public int getSubscriptionId();
+ method public int getSubscriptionType();
method public boolean isEmbedded();
method public boolean isOpportunistic();
method public void writeToParcel(android.os.Parcel, int);
@@ -42904,6 +42939,8 @@
field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX";
field public static final int INVALID_SIM_SLOT_INDEX = -1; // 0xffffffff
field public static final int INVALID_SUBSCRIPTION_ID = -1; // 0xffffffff
+ field public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; // 0x0
+ field public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; // 0x1
}
public static class SubscriptionManager.OnOpportunisticSubscriptionsChangedListener {
diff --git a/api/system-current.txt b/api/system-current.txt
index c6f000c..776889a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3060,6 +3060,7 @@
}
public class ConnectivityManager {
+ method @RequiresPermission("android.permission.PACKET_KEEPALIVE_OFFLOAD") public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull java.io.FileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
method public boolean getAvoidBadWifi();
method @RequiresPermission(android.Manifest.permission.LOCAL_MAC_ADDRESS) public String getCaptivePortalServerUrl();
method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported();
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index 88957df..10cbc07 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -493,6 +493,7 @@
{"AID_LMKD", 1069},
{"AID_LLKD", 1070},
{"AID_IORAPD", 1071},
+ {"AID_NETWORK_STACK", 1073},
{"AID_SHELL", 2000},
{"AID_CACHE", 2001},
{"AID_DIAG", 2002}};
diff --git a/config/hiddenapi-greylist.txt b/config/hiddenapi-greylist.txt
index 908a551..3d5b32a 100644
--- a/config/hiddenapi-greylist.txt
+++ b/config/hiddenapi-greylist.txt
@@ -3612,7 +3612,7 @@
Lcom/android/internal/telephony/SubscriptionController;->mLock:Ljava/lang/Object;
Lcom/android/internal/telephony/SubscriptionController;->notifySubscriptionInfoChanged()V
Lcom/android/internal/telephony/SubscriptionController;->setDefaultDataSubId(I)V
-Lcom/android/internal/telephony/SubscriptionController;->setDefaultFallbackSubId(I)V
+Lcom/android/internal/telephony/SubscriptionController;->setDefaultFallbackSubId(II)V
Lcom/android/internal/telephony/SubscriptionController;->setDefaultSmsSubId(I)V
Lcom/android/internal/telephony/SubscriptionController;->setDefaultVoiceSubId(I)V
Lcom/android/internal/telephony/SubscriptionController;->setPlmnSpn(IZLjava/lang/String;ZLjava/lang/String;)Z
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 010a7b2..4afd520 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1900,6 +1900,20 @@
}
/**
+ * Return true if Hearing Aid Profile is supported.
+ *
+ * @return true if phone supports Hearing Aid Profile
+ */
+ private boolean isHearingAidProfileSupported() {
+ try {
+ return mManagerService.isHearingAidProfileSupported();
+ } catch (RemoteException e) {
+ Log.e(TAG, "remote expection when calling isHearingAidProfileSupported", e);
+ return false;
+ }
+ }
+
+ /**
* Get the maximum number of connected audio devices.
*
* @return the maximum number of connected audio devices
@@ -2051,6 +2065,11 @@
supportedProfiles.add(i);
}
}
+ } else {
+ // Bluetooth is disabled. Just fill in known supported Profiles
+ if (isHearingAidProfileSupported()) {
+ supportedProfiles.add(BluetoothProfile.HEARING_AID);
+ }
}
}
} catch (RemoteException e) {
@@ -2468,15 +2487,16 @@
* Get the profile proxy object associated with the profile.
*
* <p>Profile can be one of {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#A2DP},
- * {@link BluetoothProfile#GATT}, or {@link BluetoothProfile#GATT_SERVER}. Clients must
- * implement {@link BluetoothProfile.ServiceListener} to get notified of the connection status
- * and to get the proxy object.
+ * {@link BluetoothProfile#GATT}, {@link BluetoothProfile#HEARING_AID}, or {@link
+ * BluetoothProfile#GATT_SERVER}. Clients must implement {@link
+ * BluetoothProfile.ServiceListener} to get notified of the connection status and to get the
+ * proxy object.
*
* @param context Context of the application
* @param listener The service Listener for connection callbacks.
* @param profile The Bluetooth profile; either {@link BluetoothProfile#HEADSET},
- * {@link BluetoothProfile#A2DP}. {@link BluetoothProfile#GATT} or
- * {@link BluetoothProfile#GATT_SERVER}.
+ * {@link BluetoothProfile#A2DP}, {@link BluetoothProfile#GATT}, {@link
+ * BluetoothProfile#HEARING_AID} or {@link BluetoothProfile#GATT_SERVER}.
* @return true on success, false on error
*/
public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener,
@@ -2525,8 +2545,11 @@
BluetoothHidDevice hidDevice = new BluetoothHidDevice(context, listener);
return true;
} else if (profile == BluetoothProfile.HEARING_AID) {
- BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener);
- return true;
+ if (isHearingAidProfileSupported()) {
+ BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener);
+ return true;
+ }
+ return false;
} else {
return false;
}
diff --git a/core/java/android/bluetooth/BluetoothHearingAid.java b/core/java/android/bluetooth/BluetoothHearingAid.java
index 2bf7dad..23a8159 100644
--- a/core/java/android/bluetooth/BluetoothHearingAid.java
+++ b/core/java/android/bluetooth/BluetoothHearingAid.java
@@ -38,15 +38,14 @@
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
- * This class provides the public APIs to control the Bluetooth Hearing Aid
- * profile.
+ * This class provides the public APIs to control the Hearing Aid profile.
*
* <p>BluetoothHearingAid is a proxy object for controlling the Bluetooth Hearing Aid
* Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
* the BluetoothHearingAid proxy object.
*
- * <p> Each method is protected with its appropriate permission.
- * @hide
+ * <p> Android only supports one set of connected Bluetooth Hearing Aid device at a time. Each
+ * method is protected with its appropriate permission.
*/
public final class BluetoothHearingAid implements BluetoothProfile {
private static final String TAG = "BluetoothHearingAid";
@@ -55,7 +54,8 @@
/**
* Intent used to broadcast the change in connection state of the Hearing Aid
- * profile.
+ * profile. Please note that in the binaural case, there will be two different LE devices for
+ * the left and right side and each device will have their own connection state changes.S
*
* <p>This intent will have 3 extras:
* <ul>
@@ -76,27 +76,6 @@
"android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED";
/**
- * Intent used to broadcast the change in the Playing state of the Hearing Aid
- * profile.
- *
- * <p>This intent will have 3 extras:
- * <ul>
- * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
- * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
- * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
- * </ul>
- *
- * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
- * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING},
- *
- * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
- * receive.
- */
- @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
- public static final String ACTION_PLAYING_STATE_CHANGED =
- "android.bluetooth.hearingaid.profile.action.PLAYING_STATE_CHANGED";
-
- /**
* Intent used to broadcast the selection of a connected device as active.
*
* <p>This intent will have one extra:
@@ -107,6 +86,8 @@
*
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
* receive.
+ *
+ * @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@UnsupportedAppUsage
@@ -114,32 +95,38 @@
"android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED";
/**
- * Hearing Aid device is streaming music. This state can be one of
- * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
- * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
+ * This device represents Left Hearing Aid.
+ *
+ * @hide
*/
- public static final int STATE_PLAYING = 10;
-
- /**
- * Hearing Aid device is NOT streaming music. This state can be one of
- * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
- * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
- */
- public static final int STATE_NOT_PLAYING = 11;
-
- /** This device represents Left Hearing Aid. */
public static final int SIDE_LEFT = IBluetoothHearingAid.SIDE_LEFT;
- /** This device represents Right Hearing Aid. */
+ /**
+ * This device represents Right Hearing Aid.
+ *
+ * @hide
+ */
public static final int SIDE_RIGHT = IBluetoothHearingAid.SIDE_RIGHT;
- /** This device is Monaural. */
+ /**
+ * This device is Monaural.
+ *
+ * @hide
+ */
public static final int MODE_MONAURAL = IBluetoothHearingAid.MODE_MONAURAL;
- /** This device is Binaural (should receive only left or right audio). */
+ /**
+ * This device is Binaural (should receive only left or right audio).
+ *
+ * @hide
+ */
public static final int MODE_BINAURAL = IBluetoothHearingAid.MODE_BINAURAL;
- /** Can't read ClientID for this device */
+ /**
+ * Indicates the HiSyncID could not be read and is unavailable.
+ *
+ * @hide
+ */
public static final long HI_SYNC_ID_INVALID = IBluetoothHearingAid.HI_SYNC_ID_INVALID;
private Context mContext;
@@ -235,12 +222,6 @@
}
}
- @Override
- public void finalize() {
- // The empty finalize needs to be kept or the
- // cts signature tests would fail.
- }
-
/**
* Initiate connection to a profile of the remote bluetooth device.
*
@@ -537,10 +518,6 @@
return "connected";
case STATE_DISCONNECTING:
return "disconnecting";
- case STATE_PLAYING:
- return "playing";
- case STATE_NOT_PLAYING:
- return "not playing";
default:
return "<unknown state " + state + ">";
}
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index 3c87c73..b8670db 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -185,7 +185,6 @@
/**
* Hearing Aid Device
*
- * @hide
*/
int HEARING_AID = 21;
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 83e8785..2130c39 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2188,6 +2188,13 @@
public static final String FEATURE_TELEPHONY_MBMS = "android.hardware.telephony.mbms";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+ * supports attaching to IMS implementations using the ImsService API in telephony.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims";
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and
* {@link #hasSystemFeature}: The device supports connecting to USB devices
* as the USB host.
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index c809cca..5bb24ba 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -15,6 +15,9 @@
*/
package android.net;
+import static android.net.IpSecManager.INVALID_RESOURCE_ID;
+
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -28,6 +31,8 @@
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.net.IpSecManager.UdpEncapsulationSocket;
+import android.net.SocketKeepalive.Callback;
import android.os.Binder;
import android.os.Build;
import android.os.Build.VERSION_CODES;
@@ -58,6 +63,7 @@
import libcore.net.event.NetworkEventDispatcher;
+import java.io.FileDescriptor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.InetAddress;
@@ -66,6 +72,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Executor;
/**
* Class that answers queries about the state of network connectivity. It also
@@ -1699,6 +1706,8 @@
* {@link PacketKeepaliveCallback#onStopped} if the operation was successful or
* {@link PacketKeepaliveCallback#onError} if an error occurred.
*
+ * @deprecated Use {@link SocketKeepalive} instead.
+ *
* @hide
*/
public class PacketKeepalive {
@@ -1802,6 +1811,8 @@
/**
* Starts an IPsec NAT-T keepalive packet with the specified parameters.
*
+ * @deprecated Use {@link #createSocketKeepalive} instead.
+ *
* @hide
*/
@UnsupportedAppUsage
@@ -1821,6 +1832,62 @@
}
/**
+ * Request that keepalives be started on a IPsec NAT-T socket.
+ *
+ * @param network The {@link Network} the socket is on.
+ * @param socket The socket that needs to be kept alive.
+ * @param source The source address of the {@link UdpEncapsulationSocket}.
+ * @param destination The destination address of the {@link UdpEncapsulationSocket}.
+ * @param executor The executor on which callback will be invoked. The provided {@link Executor}
+ * must run callback sequentially, otherwise the order of callbacks cannot be
+ * guaranteed.
+ * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive
+ * changes. Must be extended by applications that use this API.
+ *
+ * @return A {@link SocketKeepalive} object, which can be used to control this keepalive object.
+ **/
+ public SocketKeepalive createSocketKeepalive(@NonNull Network network,
+ @NonNull UdpEncapsulationSocket socket,
+ @NonNull InetAddress source,
+ @NonNull InetAddress destination,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Callback callback) {
+ return new NattSocketKeepalive(mService, network, socket.getFileDescriptor(),
+ socket.getResourceId(), source, destination, executor, callback);
+ }
+
+ /**
+ * Request that keepalives be started on a IPsec NAT-T socket file descriptor. Directly called
+ * by system apps which don't use IpSecService to create {@link UdpEncapsulationSocket}.
+ *
+ * @param network The {@link Network} the socket is on.
+ * @param fd The {@link FileDescriptor} that needs to be kept alive. The provided
+ * {@link FileDescriptor} must be bound to a port and the keepalives will be sent from
+ * that port.
+ * @param source The source address of the {@link UdpEncapsulationSocket}.
+ * @param destination The destination address of the {@link UdpEncapsulationSocket}. The
+ * keepalive packets will always be sent to port 4500 of the given {@code destination}.
+ * @param executor The executor on which callback will be invoked. The provided {@link Executor}
+ * must run callback sequentially, otherwise the order of callbacks cannot be
+ * guaranteed.
+ * @param callback A {@link SocketKeepalive.Callback}. Used for notifications about keepalive
+ * changes. Must be extended by applications that use this API.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD)
+ public SocketKeepalive createNattKeepalive(@NonNull Network network,
+ @NonNull FileDescriptor fd,
+ @NonNull InetAddress source,
+ @NonNull InetAddress destination,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Callback callback) {
+ return new NattSocketKeepalive(mService, network, fd, INVALID_RESOURCE_ID /* Unused */,
+ source, destination, executor, callback);
+ }
+
+ /**
* Ensure that a network route exists to deliver traffic to the specified
* host via the specified network interface. An attempt to add a route that
* already exists is ignored, but treated as successful.
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 131925e..e97060a 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -181,6 +181,10 @@
void startNattKeepalive(in Network network, int intervalSeconds, in Messenger messenger,
in IBinder binder, String srcAddr, int srcPort, String dstAddr);
+ void startNattKeepaliveWithFd(in Network network, in FileDescriptor fd, int resourceId,
+ int intervalSeconds, in Messenger messenger, in IBinder binder, String srcAddr,
+ String dstAddr);
+
void stopKeepalive(in Network network, int slot);
String getCaptivePortalServerUrl();
diff --git a/core/java/android/net/NattSocketKeepalive.java b/core/java/android/net/NattSocketKeepalive.java
new file mode 100644
index 0000000..88631ae
--- /dev/null
+++ b/core/java/android/net/NattSocketKeepalive.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.net.InetAddress;
+import java.util.concurrent.Executor;
+
+/** @hide */
+public final class NattSocketKeepalive extends SocketKeepalive {
+ /** The NAT-T destination port for IPsec */
+ public static final int NATT_PORT = 4500;
+
+ @NonNull private final InetAddress mSource;
+ @NonNull private final InetAddress mDestination;
+ @NonNull private final FileDescriptor mFd;
+ private final int mResourceId;
+
+ NattSocketKeepalive(@NonNull IConnectivityManager service,
+ @NonNull Network network,
+ @NonNull FileDescriptor fd,
+ int resourceId,
+ @NonNull InetAddress source,
+ @NonNull InetAddress destination,
+ @NonNull Executor executor,
+ @NonNull Callback callback) {
+ super(service, network, executor, callback);
+ mSource = source;
+ mDestination = destination;
+ mFd = fd;
+ mResourceId = resourceId;
+ }
+
+ @Override
+ void startImpl(int intervalSec) {
+ try {
+ mService.startNattKeepaliveWithFd(mNetwork, mFd, mResourceId, intervalSec, mMessenger,
+ new Binder(), mSource.getHostAddress(), mDestination.getHostAddress());
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error starting packet keepalive: ", e);
+ stopLooper();
+ }
+ }
+
+ @Override
+ void stopImpl() {
+ try {
+ if (mSlot != null) {
+ mService.stopKeepalive(mNetwork, mSlot);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error stopping packet keepalive: ", e);
+ stopLooper();
+ }
+ }
+}
diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java
new file mode 100644
index 0000000..97d50f4
--- /dev/null
+++ b/core/java/android/net/SocketKeepalive.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Process;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * Allows applications to request that the system periodically send specific packets on their
+ * behalf, using hardware offload to save battery power.
+ *
+ * To request that the system send keepalives, call one of the methods that return a
+ * {@link SocketKeepalive} object, such as {@link ConnectivityManager#createSocketKeepalive},
+ * passing in a non-null callback. If the {@link SocketKeepalive} is successfully
+ * started, the callback's {@code onStarted} method will be called. If an error occurs,
+ * {@code onError} will be called, specifying one of the {@code ERROR_*} constants in this
+ * class.
+ *
+ * To stop an existing keepalive, call {@link SocketKeepalive#stop}. The system will call
+ * {@link SocketKeepalive.Callback#onStopped} if the operation was successful or
+ * {@link SocketKeepalive.Callback#onError} if an error occurred.
+ */
+public abstract class SocketKeepalive implements AutoCloseable {
+ static final String TAG = "SocketKeepalive";
+
+ /** @hide */
+ public static final int SUCCESS = 0;
+
+ /** @hide */
+ public static final int NO_KEEPALIVE = -1;
+
+ /** @hide */
+ public static final int DATA_RECEIVED = -2;
+
+ /** @hide */
+ public static final int BINDER_DIED = -10;
+
+ /** The specified {@code Network} is not connected. */
+ public static final int ERROR_INVALID_NETWORK = -20;
+ /** The specified IP addresses are invalid. For example, the specified source IP address is
+ * not configured on the specified {@code Network}. */
+ public static final int ERROR_INVALID_IP_ADDRESS = -21;
+ /** The requested port is invalid. */
+ public static final int ERROR_INVALID_PORT = -22;
+ /** The packet length is invalid (e.g., too long). */
+ public static final int ERROR_INVALID_LENGTH = -23;
+ /** The packet transmission interval is invalid (e.g., too short). */
+ public static final int ERROR_INVALID_INTERVAL = -24;
+ /** The target socket is invalid. */
+ public static final int ERROR_INVALID_SOCKET = -25;
+ /** The target socket is not idle. */
+ public static final int ERROR_SOCKET_NOT_IDLE = -26;
+
+ /** The hardware does not support this request. */
+ public static final int ERROR_HARDWARE_UNSUPPORTED = -30;
+ /** The hardware returned an error. */
+ public static final int ERROR_HARDWARE_ERROR = -31;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = { "ERROR_" }, value = {
+ ERROR_INVALID_NETWORK,
+ ERROR_INVALID_IP_ADDRESS,
+ ERROR_INVALID_PORT,
+ ERROR_INVALID_LENGTH,
+ ERROR_INVALID_INTERVAL,
+ ERROR_INVALID_SOCKET,
+ ERROR_SOCKET_NOT_IDLE
+ })
+ public @interface ErrorCode {}
+
+ /**
+ * The minimum interval in seconds between keepalive packet transmissions.
+ *
+ * @hide
+ **/
+ public static final int MIN_INTERVAL_SEC = 10;
+
+ /**
+ * The maximum interval in seconds between keepalive packet transmissions.
+ *
+ * @hide
+ **/
+ public static final int MAX_INTERVAL_SEC = 3600;
+
+ @NonNull final IConnectivityManager mService;
+ @NonNull final Network mNetwork;
+ @NonNull private final Executor mExecutor;
+ @NonNull private final SocketKeepalive.Callback mCallback;
+ @NonNull private final Looper mLooper;
+ @NonNull final Messenger mMessenger;
+ @NonNull Integer mSlot;
+
+ SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network,
+ @NonNull Executor executor, @NonNull Callback callback) {
+ mService = service;
+ mNetwork = network;
+ mExecutor = executor;
+ mCallback = callback;
+ // TODO: 1. Use other thread modeling instead of create one thread for every instance to
+ // reduce the memory cost.
+ // 2. support restart.
+ // 3. Fix race condition which caused by rapidly start and stop.
+ HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND
+ + Process.THREAD_PRIORITY_LESS_FAVORABLE);
+ thread.start();
+ mLooper = thread.getLooper();
+ mMessenger = new Messenger(new Handler(mLooper) {
+ @Override
+ public void handleMessage(Message message) {
+ switch (message.what) {
+ case NetworkAgent.EVENT_PACKET_KEEPALIVE:
+ final int status = message.arg2;
+ try {
+ if (status == SUCCESS) {
+ if (mSlot == null) {
+ mSlot = message.arg1;
+ mExecutor.execute(() -> mCallback.onStarted());
+ } else {
+ mSlot = null;
+ stopLooper();
+ mExecutor.execute(() -> mCallback.onStopped());
+ }
+ } else if (status == DATA_RECEIVED) {
+ stopLooper();
+ mExecutor.execute(() -> mCallback.onDataReceived());
+ } else {
+ stopLooper();
+ mExecutor.execute(() -> mCallback.onError(status));
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception in keepalive callback(" + status + ")", e);
+ }
+ break;
+ default:
+ Log.e(TAG, "Unhandled message " + Integer.toHexString(message.what));
+ break;
+ }
+ }
+ });
+ }
+
+ /**
+ * Request that keepalive be started with the given {@code intervalSec}. See
+ * {@link SocketKeepalive}.
+ *
+ * @param intervalSec The target interval in seconds between keepalive packet transmissions.
+ * The interval should be between 10 seconds and 3600 seconds, otherwise
+ * {@link #ERROR_INVALID_INTERVAL} will be returned.
+ */
+ public final void start(@IntRange(from = MIN_INTERVAL_SEC, to = MAX_INTERVAL_SEC)
+ int intervalSec) {
+ startImpl(intervalSec);
+ }
+
+ abstract void startImpl(int intervalSec);
+
+ /** @hide */
+ protected void stopLooper() {
+ // TODO: remove this after changing thread modeling.
+ mLooper.quit();
+ }
+
+ /**
+ * Requests that keepalive be stopped. The application must wait for {@link Callback#onStopped}
+ * before using the object. See {@link SocketKeepalive}.
+ */
+ public final void stop() {
+ stopImpl();
+ }
+
+ abstract void stopImpl();
+
+ /**
+ * Deactivate this {@link SocketKeepalive} and free allocated resources. The instance won't be
+ * usable again if {@code close()} is called.
+ */
+ @Override
+ public final void close() {
+ stop();
+ stopLooper();
+ }
+
+ /**
+ * The callback which app can use to learn the status changes of {@link SocketKeepalive}. See
+ * {@link SocketKeepalive}.
+ */
+ public static class Callback {
+ /** The requested keepalive was successfully started. */
+ public void onStarted() {}
+ /** The keepalive was successfully stopped. */
+ public void onStopped() {}
+ /** An error occurred. */
+ public void onError(@ErrorCode int error) {}
+ /** The keepalive on a TCP socket was stopped because the socket received data. */
+ public void onDataReceived() {}
+ }
+}
diff --git a/core/jni/android_util_Process.cpp b/core/jni/android_util_Process.cpp
index 9782541..62aa1f38 100644
--- a/core/jni/android_util_Process.cpp
+++ b/core/jni/android_util_Process.cpp
@@ -22,10 +22,10 @@
#include <utils/Log.h>
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
+#include <cutils/sched_policy.h>
#include <utils/String8.h>
#include <utils/Vector.h>
#include <processgroup/processgroup.h>
-#include <processgroup/sched_policy.h>
#include "core_jni_helpers.h"
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 569a894..43e6399 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -69,13 +69,13 @@
#include <android-base/unique_fd.h>
#include <cutils/fs.h>
#include <cutils/multiuser.h>
+#include <cutils/sched_policy.h>
#include <private/android_filesystem_config.h>
#include <utils/String8.h>
#include <selinux/android.h>
#include <seccomp_policy.h>
#include <stats_event_list.h>
#include <processgroup/processgroup.h>
-#include <processgroup/sched_policy.h>
#include "core_jni_helpers.h"
#include <nativehelper/JNIHelp.h>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 8ea2ab7..7587986 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -122,6 +122,9 @@
be sent during a change to the audio output device. -->
<bool name="config_sendAudioBecomingNoisy">true</bool>
+ <!-- Whether Hearing Aid profile is supported -->
+ <bool name="config_hearing_aid_profile_supported">true</bool>
+
<!-- Flag to disable all transition animations -->
<bool name="config_disableTransitionAnimation">false</bool>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 310aaf4..8d832ca 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -434,6 +434,7 @@
<java-symbol type="integer" name="config_bluetooth_operating_voltage_mv" />
<java-symbol type="bool" name="config_bluetooth_pan_enable_autoconnect" />
<java-symbol type="bool" name="config_bluetooth_reload_supported_profiles_when_enabled" />
+ <java-symbol type="bool" name="config_hearing_aid_profile_supported" />
<java-symbol type="integer" name="config_cursorWindowSize" />
<java-symbol type="integer" name="config_drawLockTimeoutMillis" />
<java-symbol type="integer" name="config_doublePressOnPowerBehavior" />
diff --git a/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java b/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java
new file mode 100644
index 0000000..941cfdb
--- /dev/null
+++ b/core/tests/coretests/src/com/android/server/wm/test/filters/CoreTestsFilter.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.test.filters;
+
+import android.os.Bundle;
+
+import com.android.test.filters.SelectTest;
+
+/**
+ * JUnit test filter that select Window Manager Service related tests from FrameworksCoreTests.
+ *
+ * <p>Use this filter when running FrameworksCoreTests as
+ * <pre>
+ * adb shell am instrument -w \
+ * -e filter com.android.server.wm.test.filters.CoreTestsFilter \
+ * -e selectTest_verbose true \
+ * com.android.frameworks.coretests/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ */
+public final class CoreTestsFilter extends SelectTest {
+
+ private static final String[] SELECTED_CORE_TESTS = {
+ "android.app.servertransaction.", // all tests under the package.
+ "android.view.DisplayCutoutTest",
+ };
+
+ public CoreTestsFilter(Bundle testArgs) {
+ super(addSelectTest(testArgs, SELECTED_CORE_TESTS));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index 330ee8f..326147d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -527,7 +527,8 @@
@VisibleForTesting
void doUpdateMobileControllers() {
- List<SubscriptionInfo> subscriptions = mSubscriptionManager.getActiveSubscriptionInfoList();
+ List<SubscriptionInfo> subscriptions = mSubscriptionManager
+ .getActiveSubscriptionInfoList(true);
if (subscriptions == null) {
subscriptions = Collections.emptyList();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index 35f0dba..55b4d27 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -182,6 +182,7 @@
subs.add(subscription);
}
when(mMockSm.getActiveSubscriptionInfoList()).thenReturn(subs);
+ when(mMockSm.getActiveSubscriptionInfoList(anyBoolean())).thenReturn(subs);
mNetworkController.doUpdateMobileControllers();
}
diff --git a/services/core/java/com/android/server/BluetoothManagerService.java b/services/core/java/com/android/server/BluetoothManagerService.java
index 5ea3390..b60dd0f 100644
--- a/services/core/java/com/android/server/BluetoothManagerService.java
+++ b/services/core/java/com/android/server/BluetoothManagerService.java
@@ -208,6 +208,8 @@
private int mErrorRecoveryRetryCounter;
private final int mSystemUiUid;
+ private boolean mIsHearingAidProfileSupported;
+
// Save a ProfileServiceConnections object for each of the bound
// bluetooth profile services
private final Map<Integer, ProfileServiceConnections> mProfileServices = new HashMap<>();
@@ -391,13 +393,19 @@
mCallbacks = new RemoteCallbackList<IBluetoothManagerCallback>();
mStateChangeCallbacks = new RemoteCallbackList<IBluetoothStateChangeCallback>();
+ mIsHearingAidProfileSupported = context.getResources()
+ .getBoolean(com.android.internal.R.bool.config_hearing_aid_profile_supported);
+
// TODO: We need a more generic way to initialize the persist keys of FeatureFlagUtils
- boolean isHearingAidEnabled;
String value = SystemProperties.get(FeatureFlagUtils.PERSIST_PREFIX + FeatureFlagUtils.HEARING_AID_SETTINGS);
if (!TextUtils.isEmpty(value)) {
- isHearingAidEnabled = Boolean.parseBoolean(value);
+ boolean isHearingAidEnabled = Boolean.parseBoolean(value);
Log.v(TAG, "set feature flag HEARING_AID_SETTINGS to " + isHearingAidEnabled);
FeatureFlagUtils.setEnabled(context, FeatureFlagUtils.HEARING_AID_SETTINGS, isHearingAidEnabled);
+ if (isHearingAidEnabled && !mIsHearingAidProfileSupported) {
+ // Overwrite to enable support by FeatureFlag
+ mIsHearingAidProfileSupported = true;
+ }
}
IntentFilter filter = new IntentFilter();
@@ -679,6 +687,11 @@
return false;
}
+ @Override
+ public boolean isHearingAidProfileSupported() {
+ return mIsHearingAidProfileSupported;
+ }
+
// Monitor change of BLE scan only mode settings.
private void registerForBleScanModeChange() {
ContentObserver contentObserver = new ContentObserver(null) {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index aa74600..fda7279 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -73,6 +73,7 @@
import android.net.LinkProperties;
import android.net.LinkProperties.CompareResult;
import android.net.MatchAllNetworkSpecifier;
+import android.net.NattSocketKeepalive;
import android.net.Network;
import android.net.NetworkAgent;
import android.net.NetworkCapabilities;
@@ -6185,6 +6186,17 @@
}
@Override
+ public void startNattKeepaliveWithFd(Network network, FileDescriptor fd, int resourceId,
+ int intervalSeconds, Messenger messenger, IBinder binder, String srcAddr,
+ String dstAddr) {
+ enforceKeepalivePermission();
+ mKeepaliveTracker.startNattKeepalive(
+ getNetworkAgentInfoForNetwork(network), fd, resourceId,
+ intervalSeconds, messenger, binder,
+ srcAddr, dstAddr, NattSocketKeepalive.NATT_PORT);
+ }
+
+ @Override
public void stopKeepalive(Network network, int slot) {
mHandler.sendMessage(mHandler.obtainMessage(
NetworkAgent.CMD_STOP_PACKET_KEEPALIVE, slot, PacketKeepalive.SUCCESS, network));
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 122112b..1798f38 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -2116,6 +2116,16 @@
android.Manifest.permission.READ_PRECISE_PHONE_STATE, null);
}
+ if ((events & PhoneStateListener.LISTEN_RADIO_POWER_STATE_CHANGED) != 0) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
+ }
+
+ if ((events & PhoneStateListener.LISTEN_VOICE_ACTIVATION_STATE) != 0) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, null);
+ }
+
return true;
}
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 8a3cdca..1559ba8 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -16,40 +16,49 @@
package com.android.server.connectivity;
-import com.android.internal.util.HexDump;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.connectivity.NetworkAgentInfo;
-import android.net.ConnectivityManager;
+// TODO: Clean up imports and remove references of PacketKeepalive constants.
+
+import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_INTERVAL;
+import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_IP_ADDRESS;
+import static android.net.ConnectivityManager.PacketKeepalive.ERROR_INVALID_NETWORK;
+import static android.net.ConnectivityManager.PacketKeepalive.MIN_INTERVAL;
+import static android.net.ConnectivityManager.PacketKeepalive.NATT_PORT;
+import static android.net.ConnectivityManager.PacketKeepalive.NO_KEEPALIVE;
+import static android.net.ConnectivityManager.PacketKeepalive.SUCCESS;
+import static android.net.NetworkAgent.CMD_START_PACKET_KEEPALIVE;
+import static android.net.NetworkAgent.CMD_STOP_PACKET_KEEPALIVE;
+import static android.net.NetworkAgent.EVENT_PACKET_KEEPALIVE;
+import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.net.ConnectivityManager.PacketKeepalive;
import android.net.KeepalivePacketData;
-import android.net.LinkAddress;
import android.net.NetworkAgent;
import android.net.NetworkUtils;
import android.net.util.IpUtils;
import android.os.Binder;
-import android.os.IBinder;
import android.os.Handler;
+import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.Process;
import android.os.RemoteException;
-import android.system.OsConstants;
+import android.system.ErrnoException;
+import android.system.Os;
import android.util.Log;
import android.util.Pair;
-import java.nio.ByteBuffer;
-import java.nio.ByteOrder;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
+import com.android.internal.util.HexDump;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
-import static android.net.ConnectivityManager.PacketKeepalive.*;
-import static android.net.NetworkAgent.CMD_START_PACKET_KEEPALIVE;
-import static android.net.NetworkAgent.CMD_STOP_PACKET_KEEPALIVE;
-import static android.net.NetworkAgent.EVENT_PACKET_KEEPALIVE;
-
/**
* Manages packet keepalive requests.
*
@@ -300,7 +309,9 @@
}
}
- public void handleEventPacketKeepalive(NetworkAgentInfo nai, Message message) {
+ /** Handle keepalive events from lower layer. */
+ public void handleEventPacketKeepalive(@NonNull NetworkAgentInfo nai,
+ @NonNull Message message) {
int slot = message.arg1;
int reason = message.arg2;
@@ -330,8 +341,18 @@
}
}
- public void startNattKeepalive(NetworkAgentInfo nai, int intervalSeconds, Messenger messenger,
- IBinder binder, String srcAddrString, int srcPort, String dstAddrString, int dstPort) {
+ /**
+ * Called when requesting that keepalives be started on a IPsec NAT-T socket. See
+ * {@link android.net.SocketKeepalive}.
+ **/
+ public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
+ int intervalSeconds,
+ @NonNull Messenger messenger,
+ @NonNull IBinder binder,
+ @NonNull String srcAddrString,
+ int srcPort,
+ @NonNull String dstAddrString,
+ int dstPort) {
if (nai == null) {
notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_NETWORK);
return;
@@ -360,6 +381,56 @@
NetworkAgent.CMD_START_PACKET_KEEPALIVE, ki).sendToTarget();
}
+ /**
+ * Called when requesting that keepalives be started on a IPsec NAT-T socket. This function is
+ * identical to {@link #startNattKeepalive}, but also takes a {@code resourceId}, which is the
+ * resource index bound to the {@link UdpEncapsulationSocket} when creating by
+ * {@link com.android.server.IpSecService} to verify whether the given
+ * {@link UdpEncapsulationSocket} is legitimate.
+ **/
+ public void startNattKeepalive(@Nullable NetworkAgentInfo nai,
+ @Nullable FileDescriptor fd,
+ int resourceId,
+ int intervalSeconds,
+ @NonNull Messenger messenger,
+ @NonNull IBinder binder,
+ @NonNull String srcAddrString,
+ @NonNull String dstAddrString,
+ int dstPort) {
+ // Ensure that the socket is created by IpSecService.
+ if (!isNattKeepaliveSocketValid(fd, resourceId)) {
+ notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_SOCKET);
+ }
+
+ // Get src port to adopt old API.
+ int srcPort = 0;
+ try {
+ final SocketAddress srcSockAddr = Os.getsockname(fd);
+ srcPort = ((InetSocketAddress) srcSockAddr).getPort();
+ } catch (ErrnoException e) {
+ notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_SOCKET);
+ }
+
+ // Forward request to old API.
+ startNattKeepalive(nai, intervalSeconds, messenger, binder, srcAddrString, srcPort,
+ dstAddrString, dstPort);
+ }
+
+ /**
+ * Verify if the IPsec NAT-T file descriptor and resource Id hold for IPsec keepalive is valid.
+ **/
+ public static boolean isNattKeepaliveSocketValid(@Nullable FileDescriptor fd, int resourceId) {
+ // TODO: 1. confirm whether the fd is called from system api or created by IpSecService.
+ // 2. If the fd is created from the system api, check that it's bounded. And
+ // call dup to keep the fd open.
+ // 3. If the fd is created from IpSecService, check if the resource ID is valid. And
+ // hold the resource needed in IpSecService.
+ if (null == fd) {
+ return false;
+ }
+ return true;
+ }
+
public void dump(IndentingPrintWriter pw) {
pw.println("Packet keepalives:");
pw.increaseIndent();
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index c034c38..6fae9a5 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -13,6 +13,7 @@
],
static_libs: [
+ "frameworks-base-testutils",
"androidx.test.runner",
"mockito-target-minus-junit4",
"platform-test-annotations",
diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java
index 51532bc..fb8f3e7 100644
--- a/telephony/java/android/provider/Telephony.java
+++ b/telephony/java/android/provider/Telephony.java
@@ -2311,6 +2311,9 @@
/**
* Contains message parts.
+ *
+ * To avoid issues where applications might cache a part ID, the ID of a deleted part must
+ * not be reused to point at a new part.
*/
public static final class Part implements BaseColumns {
@@ -2322,6 +2325,12 @@
}
/**
+ * The {@code content://} style URL for this table. Can be appended with a part ID to
+ * address individual parts.
+ */
+ public static final Uri CONTENT_URI = Uri.withAppendedPath(Mms.CONTENT_URI, "part");
+
+ /**
* The identifier of the message which this part belongs to.
* <P>Type: INTEGER</P>
*/
diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java
index 85c53f2..ca264f7 100644
--- a/telephony/java/android/telephony/DataFailCause.java
+++ b/telephony/java/android/telephony/DataFailCause.java
@@ -961,6 +961,13 @@
/** @hide */
public static final int RESET_BY_FRAMEWORK = 0x10005;
+ /**
+ * Data handover failed.
+ *
+ * @hide
+ */
+ public static final int HANDOVER_FAILED = 0x10006;
+
/** @hide */
@IntDef(value = {
NONE,
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 421851b..10647e6 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -1410,47 +1410,49 @@
}
/** @hide */
- public static int rilRadioTechnologyToNetworkType(@RilRadioTechnology int rt) {
- switch(rt) {
- case ServiceState.RIL_RADIO_TECHNOLOGY_GPRS:
- return TelephonyManager.NETWORK_TYPE_GPRS;
- case ServiceState.RIL_RADIO_TECHNOLOGY_EDGE:
- return TelephonyManager.NETWORK_TYPE_EDGE;
- case ServiceState.RIL_RADIO_TECHNOLOGY_UMTS:
- return TelephonyManager.NETWORK_TYPE_UMTS;
- case ServiceState.RIL_RADIO_TECHNOLOGY_HSDPA:
- return TelephonyManager.NETWORK_TYPE_HSDPA;
- case ServiceState.RIL_RADIO_TECHNOLOGY_HSUPA:
- return TelephonyManager.NETWORK_TYPE_HSUPA;
- case ServiceState.RIL_RADIO_TECHNOLOGY_HSPA:
- return TelephonyManager.NETWORK_TYPE_HSPA;
- case ServiceState.RIL_RADIO_TECHNOLOGY_IS95A:
- case ServiceState.RIL_RADIO_TECHNOLOGY_IS95B:
- return TelephonyManager.NETWORK_TYPE_CDMA;
- case ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT:
- return TelephonyManager.NETWORK_TYPE_1xRTT;
- case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0:
- return TelephonyManager.NETWORK_TYPE_EVDO_0;
- case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A:
- return TelephonyManager.NETWORK_TYPE_EVDO_A;
- case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_B:
- return TelephonyManager.NETWORK_TYPE_EVDO_B;
- case ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD:
- return TelephonyManager.NETWORK_TYPE_EHRPD;
- case ServiceState.RIL_RADIO_TECHNOLOGY_LTE:
- return TelephonyManager.NETWORK_TYPE_LTE;
- case ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP:
- return TelephonyManager.NETWORK_TYPE_HSPAP;
- case ServiceState.RIL_RADIO_TECHNOLOGY_GSM:
- return TelephonyManager.NETWORK_TYPE_GSM;
- case ServiceState.RIL_RADIO_TECHNOLOGY_TD_SCDMA:
- return TelephonyManager.NETWORK_TYPE_TD_SCDMA;
- case ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN:
- return TelephonyManager.NETWORK_TYPE_IWLAN;
- case ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA:
- return TelephonyManager.NETWORK_TYPE_LTE_CA;
- default:
- return TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ public static int rilRadioTechnologyToNetworkType(@RilRadioTechnology int rat) {
+ switch(rat) {
+ case ServiceState.RIL_RADIO_TECHNOLOGY_GPRS:
+ return TelephonyManager.NETWORK_TYPE_GPRS;
+ case ServiceState.RIL_RADIO_TECHNOLOGY_EDGE:
+ return TelephonyManager.NETWORK_TYPE_EDGE;
+ case ServiceState.RIL_RADIO_TECHNOLOGY_UMTS:
+ return TelephonyManager.NETWORK_TYPE_UMTS;
+ case ServiceState.RIL_RADIO_TECHNOLOGY_HSDPA:
+ return TelephonyManager.NETWORK_TYPE_HSDPA;
+ case ServiceState.RIL_RADIO_TECHNOLOGY_HSUPA:
+ return TelephonyManager.NETWORK_TYPE_HSUPA;
+ case ServiceState.RIL_RADIO_TECHNOLOGY_HSPA:
+ return TelephonyManager.NETWORK_TYPE_HSPA;
+ case ServiceState.RIL_RADIO_TECHNOLOGY_IS95A:
+ case ServiceState.RIL_RADIO_TECHNOLOGY_IS95B:
+ return TelephonyManager.NETWORK_TYPE_CDMA;
+ case ServiceState.RIL_RADIO_TECHNOLOGY_1xRTT:
+ return TelephonyManager.NETWORK_TYPE_1xRTT;
+ case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_0:
+ return TelephonyManager.NETWORK_TYPE_EVDO_0;
+ case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A:
+ return TelephonyManager.NETWORK_TYPE_EVDO_A;
+ case ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_B:
+ return TelephonyManager.NETWORK_TYPE_EVDO_B;
+ case ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD:
+ return TelephonyManager.NETWORK_TYPE_EHRPD;
+ case ServiceState.RIL_RADIO_TECHNOLOGY_LTE:
+ return TelephonyManager.NETWORK_TYPE_LTE;
+ case ServiceState.RIL_RADIO_TECHNOLOGY_HSPAP:
+ return TelephonyManager.NETWORK_TYPE_HSPAP;
+ case ServiceState.RIL_RADIO_TECHNOLOGY_GSM:
+ return TelephonyManager.NETWORK_TYPE_GSM;
+ case ServiceState.RIL_RADIO_TECHNOLOGY_TD_SCDMA:
+ return TelephonyManager.NETWORK_TYPE_TD_SCDMA;
+ case ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN:
+ return TelephonyManager.NETWORK_TYPE_IWLAN;
+ case ServiceState.RIL_RADIO_TECHNOLOGY_LTE_CA:
+ return TelephonyManager.NETWORK_TYPE_LTE_CA;
+ case ServiceState.RIL_RADIO_TECHNOLOGY_NR:
+ return TelephonyManager.NETWORK_TYPE_NR;
+ default:
+ return TelephonyManager.NETWORK_TYPE_UNKNOWN;
}
}
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 1378bb0..d777bf1 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -22,6 +22,10 @@
import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothMapClient;
+import android.bluetooth.BluetoothProfile;
import android.content.ActivityNotFoundException;
import android.content.ContentValues;
import android.content.Context;
@@ -32,6 +36,7 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.telecom.PhoneAccount;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
@@ -61,6 +66,8 @@
*/
public final class SmsManager {
private static final String TAG = "SmsManager";
+ private static final boolean DBG = false;
+
/**
* A psuedo-subId that represents the default subId at any given time. The actual subId it
* represents changes as the default subId is changed.
@@ -339,6 +346,44 @@
throw new IllegalArgumentException("Invalid message body");
}
+ // A Manager code accessing another manager is *not* acceptable, in Android.
+ // In this particular case, it is unavoidable because of the following:
+ // If the subscription for this SmsManager instance belongs to a remote SIM
+ // then a listener to get BluetoothMapClient proxy needs to be started up.
+ // Doing that is possible only in a foreground thread or as a system user.
+ // i.e., Can't be done in ISms service.
+ // For that reason, SubscriptionManager needs to be accessed here to determine
+ // if the subscription belongs to a remote SIM.
+ // Ideally, there should be another API in ISms to service messages going thru
+ // remote SIM subscriptions (and ISms should be tweaked to be able to access
+ // BluetoothMapClient proxy)
+ Context context = ActivityThread.currentApplication().getApplicationContext();
+ SubscriptionManager manager = (SubscriptionManager) context
+ .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);
+ int subId = getSubscriptionId();
+ SubscriptionInfo info = manager.getActiveSubscriptionInfo(subId);
+ if (DBG) {
+ Log.d(TAG, "for subId: " + subId + ", subscription-info: " + info);
+ }
+ if (info == null) {
+ // There is no subscription for the given subId. That can only mean one thing:
+ // the caller is using a SmsManager instance with an obsolete subscription id.
+ // That is most probably because caller didn't invalidate SmsManager instance
+ // for an already deleted subscription id.
+ Log.e(TAG, "subId: " + subId + " for this SmsManager instance is obsolete.");
+ sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE);
+ }
+
+ /* If the Subscription associated with this SmsManager instance belongs to a remote-sim,
+ * then send the message thru the remote-sim subscription.
+ */
+ if (info.getSubscriptionType() == SubscriptionManager.SUBSCRIPTION_TYPE_REMOTE_SIM) {
+ if (DBG) Log.d(TAG, "sending message thru bluetooth");
+ sendTextMessageBluetooth(destinationAddress, scAddress, text, sentIntent,
+ deliveryIntent, info);
+ return;
+ }
+
try {
ISms iccISms = getISmsServiceOrThrow();
iccISms.sendTextForSubscriber(getSubscriptionId(), ActivityThread.currentPackageName(),
@@ -350,6 +395,79 @@
}
}
+ private void sendTextMessageBluetooth(String destAddr, String scAddress,
+ String text, PendingIntent sentIntent, PendingIntent deliveryIntent,
+ SubscriptionInfo info) {
+ BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (btAdapter == null) {
+ // No bluetooth service on this platform?
+ sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE);
+ return;
+ }
+ BluetoothDevice device = btAdapter.getRemoteDevice(info.getIccId());
+ if (device == null) {
+ if (DBG) Log.d(TAG, "Bluetooth device addr invalid: " + info.getIccId());
+ sendErrorInPendingIntent(sentIntent, SmsManager.RESULT_ERROR_NO_SERVICE);
+ return;
+ }
+ btAdapter.getProfileProxy(ActivityThread.currentApplication().getApplicationContext(),
+ new MapMessageSender(destAddr, text, device, sentIntent, deliveryIntent),
+ BluetoothProfile.MAP_CLIENT);
+ }
+
+ private class MapMessageSender implements BluetoothProfile.ServiceListener {
+ final Uri[] mDestAddr;
+ private String mMessage;
+ final BluetoothDevice mDevice;
+ final PendingIntent mSentIntent;
+ final PendingIntent mDeliveryIntent;
+ MapMessageSender(final String destAddr, final String message, final BluetoothDevice device,
+ final PendingIntent sentIntent, final PendingIntent deliveryIntent) {
+ super();
+ mDestAddr = new Uri[] {new Uri.Builder()
+ .appendPath(destAddr)
+ .scheme(PhoneAccount.SCHEME_TEL)
+ .build()};
+ mMessage = message;
+ mDevice = device;
+ mSentIntent = sentIntent;
+ mDeliveryIntent = deliveryIntent;
+ }
+
+ @Override
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ if (DBG) Log.d(TAG, "Service connected");
+ if (profile != BluetoothProfile.MAP_CLIENT) return;
+ BluetoothMapClient mapProfile = (BluetoothMapClient) proxy;
+ if (mMessage != null) {
+ if (DBG) Log.d(TAG, "Sending message thru bluetooth");
+ mapProfile.sendMessage(mDevice, mDestAddr, mMessage, mSentIntent, mDeliveryIntent);
+ mMessage = null;
+ }
+ BluetoothAdapter.getDefaultAdapter()
+ .closeProfileProxy(BluetoothProfile.MAP_CLIENT, mapProfile);
+ }
+
+ @Override
+ public void onServiceDisconnected(int profile) {
+ if (mMessage != null) {
+ if (DBG) Log.d(TAG, "Bluetooth disconnected before sending the message");
+ sendErrorInPendingIntent(mSentIntent, SmsManager.RESULT_ERROR_NO_SERVICE);
+ mMessage = null;
+ }
+ }
+ }
+
+ private void sendErrorInPendingIntent(PendingIntent intent, int errorCode) {
+ try {
+ intent.send(errorCode);
+ } catch (PendingIntent.CanceledException e) {
+ // PendingIntent is cancelled. ignore sending this error code back to
+ // caller.
+ if (DBG) Log.d(TAG, "PendingIntent.CanceledException: " + e.getMessage());
+ }
+ }
+
/**
* Send a text based SMS without writing it into the SMS Provider.
*
@@ -888,8 +1006,6 @@
}
}
-
-
/**
* Get the SmsManager associated with the default subscription id. The instance will always be
* associated with the default subscription id, even if the default subscription id is changed.
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 4a25818..4e4ef4d 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -184,6 +184,11 @@
private int mProfileClass;
/**
+ * Type of subscription
+ */
+ private int mSubscriptionType;
+
+ /**
* @hide
*/
public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
@@ -206,7 +211,8 @@
@Nullable String groupUUID, boolean isMetered, int carrierId, int profileClass) {
this(id, iccId, simSlotIndex, displayName, carrierName, nameSource, iconTint, number,
roaming, icon, mcc, mnc, countryIso, isEmbedded, accessRules, cardString, -1,
- isOpportunistic, groupUUID, isMetered, false, carrierId, profileClass);
+ isOpportunistic, groupUUID, isMetered, false, carrierId, profileClass,
+ SubscriptionManager.SUBSCRIPTION_TYPE_LOCAL_SIM);
}
/**
@@ -217,7 +223,7 @@
Bitmap icon, String mcc, String mnc, String countryIso, boolean isEmbedded,
@Nullable UiccAccessRule[] accessRules, String cardString, int cardId,
boolean isOpportunistic, @Nullable String groupUUID, boolean isMetered,
- boolean isGroupDisabled, int carrierid, int profileClass) {
+ boolean isGroupDisabled, int carrierId, int profileClass, int subType) {
this.mId = id;
this.mIccId = iccId;
this.mSimSlotIndex = simSlotIndex;
@@ -239,11 +245,11 @@
this.mGroupUUID = groupUUID;
this.mIsMetered = isMetered;
this.mIsGroupDisabled = isGroupDisabled;
- this.mCarrierId = carrierid;
+ this.mCarrierId = carrierId;
this.mProfileClass = profileClass;
+ this.mSubscriptionType = subType;
}
-
/**
* @return the subscription ID.
*/
@@ -487,6 +493,16 @@
}
/**
+ * This method returns the type of a subscription. It can be
+ * {@link SubscriptionManager#SUBSCRIPTION_TYPE_LOCAL_SIM} or
+ * {@link SubscriptionManager#SUBSCRIPTION_TYPE_REMOTE_SIM}.
+ * @return the type of subscription
+ */
+ public @SubscriptionManager.SubscriptionType int getSubscriptionType() {
+ return mSubscriptionType;
+ }
+
+ /**
* Checks whether the app with the given context is authorized to manage this subscription
* according to its metadata. Only supported for embedded subscriptions (if {@link #isEmbedded}
* returns true).
@@ -611,11 +627,12 @@
boolean isGroupDisabled = source.readBoolean();
int carrierid = source.readInt();
int profileClass = source.readInt();
+ int subType = source.readInt();
return new SubscriptionInfo(id, iccId, simSlotIndex, displayName, carrierName,
nameSource, iconTint, number, dataRoaming, iconBitmap, mcc, mnc, countryIso,
isEmbedded, accessRules, cardString, cardId, isOpportunistic, groupUUID,
- isMetered, isGroupDisabled, carrierid, profileClass);
+ isMetered, isGroupDisabled, carrierid, profileClass, subType);
}
@Override
@@ -649,6 +666,7 @@
dest.writeBoolean(mIsGroupDisabled);
dest.writeInt(mCarrierId);
dest.writeInt(mProfileClass);
+ dest.writeInt(mSubscriptionType);
}
@Override
@@ -685,7 +703,8 @@
+ " cardString=" + cardStringToPrint + " cardId=" + mCardId
+ " isOpportunistic " + mIsOpportunistic + " mGroupUUID=" + mGroupUUID
+ " isMetered=" + mIsMetered + " mIsGroupDisabled=" + mIsGroupDisabled
- + " profileClass=" + mProfileClass + "}";
+ + " profileClass=" + mProfileClass
+ + " subscriptionType=" + mSubscriptionType + "}";
}
@Override
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 6724c034..845d23e 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -247,7 +247,9 @@
public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id";
/**
- * TelephonyProvider column name for SIM ICC Identifier
+ * TelephonyProvider column name for a unique identifier for the subscription within the
+ * specific subscription type. For example, it contains SIM ICC Identifier subscriptions
+ * on Local SIMs. and Mac-address for Remote-SIM Subscriptions for Bluetooth devices.
* <P>Type: TEXT (String)</P>
*/
/** @hide */
@@ -265,6 +267,63 @@
public static final int SIM_NOT_INSERTED = -1;
/**
+ * The slot-index for Bluetooth Remote-SIM subscriptions
+ * @hide
+ */
+ public static final int SLOT_INDEX_FOR_REMOTE_SIM_SUB = INVALID_SIM_SLOT_INDEX;
+
+ /**
+ * TelephonyProvider column name Subscription-type.
+ * <P>Type: INTEGER (int)</P> {@link #SUBSCRIPTION_TYPE_LOCAL_SIM} for Local-SIM Subscriptions,
+ * {@link #SUBSCRIPTION_TYPE_REMOTE_SIM} for Remote-SIM Subscriptions.
+ * Default value is 0.
+ */
+ /** @hide */
+ public static final String SUBSCRIPTION_TYPE = "subscription_type";
+
+ /**
+ * This constant is to designate a subscription as a Local-SIM Subscription.
+ * <p> A Local-SIM can be a physical SIM inserted into a sim-slot in the device, or eSIM on the
+ * device.
+ * </p>
+ */
+ public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0;
+
+ /**
+ * This constant is to designate a subscription as a Remote-SIM Subscription.
+ * <p>
+ * A Remote-SIM subscription is for a SIM on a phone connected to this device via some
+ * connectivity mechanism, for example bluetooth. Similar to Local SIM, this subscription can
+ * be used for SMS, Voice and data by proxying data through the connected device.
+ * Certain data of the SIM, such as IMEI, are not accessible for Remote SIMs.
+ * </p>
+ *
+ * <p>
+ * A Remote-SIM is available only as long the phone stays connected to this device.
+ * When the phone disconnects, Remote-SIM subscription is removed from this device and is
+ * no longer known. All data associated with the subscription, such as stored SMS, call logs,
+ * contacts etc, are removed from this device.
+ * </p>
+ *
+ * <p>
+ * If the phone re-connects to this device, a new Remote-SIM subscription is created for
+ * the phone. The Subscription Id associated with the new subscription is different from
+ * the Subscription Id of the previous Remote-SIM subscription created (and removed) for the
+ * phone; i.e., new Remote-SIM subscription treats the reconnected phone as a Remote-SIM that
+ * was never seen before.
+ * </p>
+ */
+ public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"SUBSCRIPTION_TYPE_"},
+ value = {
+ SUBSCRIPTION_TYPE_LOCAL_SIM,
+ SUBSCRIPTION_TYPE_REMOTE_SIM})
+ public @interface SubscriptionType {}
+
+ /**
* TelephonyProvider column name for user displayed name.
* <P>Type: TEXT (String)</P>
*/
@@ -1146,7 +1205,7 @@
}
/**
- * Get the SubscriptionInfo(s) of the currently inserted SIM(s). The records will be sorted
+ * Get the SubscriptionInfo(s) of the currently active SIM(s). The records will be sorted
* by {@link SubscriptionInfo#getSimSlotIndex} then by {@link SubscriptionInfo#getSubscriptionId}.
*
* <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
@@ -1428,17 +1487,7 @@
logd("[addSubscriptionInfoRecord]- invalid slotIndex");
}
- try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
- if (iSub != null) {
- // FIXME: This returns 1 on success, 0 on error should should we return it?
- iSub.addSubInfoRecord(iccId, slotIndex);
- } else {
- logd("[addSubscriptionInfoRecord]- ISub service is null");
- }
- } catch (RemoteException ex) {
- // ignore it
- }
+ addSubscriptionInfoRecord(iccId, null, slotIndex, SUBSCRIPTION_TYPE_LOCAL_SIM);
// FIXME: Always returns null?
return null;
@@ -1446,6 +1495,79 @@
}
/**
+ * Add a new SubscriptionInfo to SubscriptionInfo database if needed
+ * @param uniqueId This is the unique identifier for the subscription within the
+ * specific subscription type.
+ * @param displayName human-readable name of the device the subscription corresponds to.
+ * @param slotIndex the slot assigned to this subscription. It is ignored for subscriptionType
+ * of {@link #SUBSCRIPTION_TYPE_REMOTE_SIM}.
+ * @param subscriptionType the {@link #SUBSCRIPTION_TYPE}
+ * @hide
+ */
+ public void addSubscriptionInfoRecord(String uniqueId, String displayName, int slotIndex,
+ int subscriptionType) {
+ if (VDBG) {
+ logd("[addSubscriptionInfoRecord]+ uniqueId:" + uniqueId
+ + ", displayName:" + displayName + ", slotIndex:" + slotIndex
+ + ", subscriptionType: " + subscriptionType);
+ }
+ if (uniqueId == null) {
+ Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- uniqueId is null");
+ return;
+ }
+
+ try {
+ ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ if (iSub == null) {
+ Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- ISub service is null");
+ return;
+ }
+ int result = iSub.addSubInfo(uniqueId, displayName, slotIndex, subscriptionType);
+ if (result < 0) {
+ Log.e(LOG_TAG, "Adding of subscription didn't succeed: error = " + result);
+ } else {
+ logd("successfully added new subscription");
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
+ * Remove SubscriptionInfo record from the SubscriptionInfo database
+ * @param uniqueId This is the unique identifier for the subscription within the specific
+ * subscription type.
+ * @param subscriptionType the {@link #SUBSCRIPTION_TYPE}
+ * @hide
+ */
+ public void removeSubscriptionInfoRecord(String uniqueId, int subscriptionType) {
+ if (VDBG) {
+ logd("[removeSubscriptionInfoRecord]+ uniqueId:" + uniqueId
+ + ", subscriptionType: " + subscriptionType);
+ }
+ if (uniqueId == null) {
+ Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- uniqueId is null");
+ return;
+ }
+
+ try {
+ ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ if (iSub == null) {
+ Log.e(LOG_TAG, "[removeSubscriptionInfoRecord]- ISub service is null");
+ return;
+ }
+ int result = iSub.removeSubInfo(uniqueId, subscriptionType);
+ if (result < 0) {
+ Log.e(LOG_TAG, "Removal of subscription didn't succeed: error = " + result);
+ } else {
+ logd("successfully removed subscription");
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ }
+
+ /**
* Set SIM icon tint color by simInfo index
* @param tint the RGB value of icon tint color of the SIM
* @param subId the unique SubInfoRecord index in database
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 5124205..b9ffd4d 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -835,6 +835,7 @@
* @see TelephonyManager#NETWORK_TYPE_LTE
* @see TelephonyManager#NETWORK_TYPE_EHRPD
* @see TelephonyManager#NETWORK_TYPE_HSPAP
+ * @see TelephonyManager#NETWORK_TYPE_NR
*
* <p class="note">
* Retrieve with
@@ -2306,6 +2307,7 @@
* @see #NETWORK_TYPE_LTE
* @see #NETWORK_TYPE_EHRPD
* @see #NETWORK_TYPE_HSPAP
+ * @see #NETWORK_TYPE_NR
*
* @hide
*/
@@ -2357,6 +2359,7 @@
* @see #NETWORK_TYPE_LTE
* @see #NETWORK_TYPE_EHRPD
* @see #NETWORK_TYPE_HSPAP
+ * @see #NETWORK_TYPE_NR
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
@RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@@ -2543,6 +2546,8 @@
return "IWLAN";
case NETWORK_TYPE_LTE_CA:
return "LTE_CA";
+ case NETWORK_TYPE_NR:
+ return "NR";
default:
return "UNKNOWN";
}
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index 577ddbd..04ec3d1 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -52,8 +52,8 @@
/**
* Get the active SubscriptionInfo associated with the slotIndex
* @param slotIndex the slot which the subscription is inserted
- * @param callingPackage The package maing the call.
- * @return SubscriptionInfo, maybe null if its not active
+ * @param callingPackage The package making the call.
+ * @return SubscriptionInfo, null for Remote-SIMs or non-active slotIndex.
*/
SubscriptionInfo getActiveSubscriptionInfoForSimSlotIndex(int slotIndex, String callingPackage);
@@ -115,6 +115,26 @@
int addSubInfoRecord(String iccId, int slotIndex);
/**
+ * Add a new subscription info record, if needed
+ * @param uniqueId This is the unique identifier for the subscription within the specific
+ * subscription type.
+ * @param displayName human-readable name of the device the subscription corresponds to.
+ * @param slotIndex the slot assigned to this device
+ * @param subscriptionType the type of subscription to be added.
+ * @return 0 if success, < 0 on error.
+ */
+ int addSubInfo(String uniqueId, String displayName, int slotIndex, int subscriptionType);
+
+ /**
+ * Remove subscription info record for the given device.
+ * @param uniqueId This is the unique identifier for the subscription within the specific
+ * subscription type.
+ * @param subscriptionType the type of subscription to be removed
+ * @return 0 if success, < 0 on error.
+ */
+ int removeSubInfo(String uniqueId, int subscriptionType);
+
+ /**
* Set SIM icon tint color by simInfo index
* @param tint the icon tint color of the SIM
* @param subId the unique SubscriptionInfo index in database
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index dda4481..b5d5f61 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -107,6 +107,8 @@
import android.net.INetworkStatsService;
import android.net.InterfaceConfiguration;
import android.net.IpPrefix;
+import android.net.IpSecManager;
+import android.net.IpSecManager.UdpEncapsulationSocket;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.MatchAllNetworkSpecifier;
@@ -122,6 +124,7 @@
import android.net.NetworkStack;
import android.net.NetworkUtils;
import android.net.RouteInfo;
+import android.net.SocketKeepalive;
import android.net.UidRange;
import android.net.metrics.IpConnectivityLog;
import android.net.shared.NetworkMonitorUtils;
@@ -186,6 +189,8 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -402,8 +407,8 @@
private final ConditionVariable mPreventReconnectReceived = new ConditionVariable();
private int mScore;
private NetworkAgent mNetworkAgent;
- private int mStartKeepaliveError = PacketKeepalive.ERROR_HARDWARE_UNSUPPORTED;
- private int mStopKeepaliveError = PacketKeepalive.NO_KEEPALIVE;
+ private int mStartKeepaliveError = SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED;
+ private int mStopKeepaliveError = SocketKeepalive.NO_KEEPALIVE;
private Integer mExpectedKeepaliveSlot = null;
// Contains the redirectUrl from networkStatus(). Before reading, wait for
// mNetworkStatusReceived.
@@ -3548,6 +3553,80 @@
}
}
+ private static class TestSocketKeepaliveCallback extends SocketKeepalive.Callback {
+
+ public enum CallbackType { ON_STARTED, ON_STOPPED, ON_ERROR };
+
+ private class CallbackValue {
+ public CallbackType callbackType;
+ public int error;
+
+ CallbackValue(CallbackType type) {
+ this.callbackType = type;
+ this.error = SocketKeepalive.SUCCESS;
+ assertTrue("onError callback must have error", type != CallbackType.ON_ERROR);
+ }
+
+ CallbackValue(CallbackType type, int error) {
+ this.callbackType = type;
+ this.error = error;
+ assertEquals("error can only be set for onError", type, CallbackType.ON_ERROR);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof CallbackValue
+ && this.callbackType == ((CallbackValue) o).callbackType
+ && this.error == ((CallbackValue) o).error;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s(%s, %d)", getClass().getSimpleName(), callbackType,
+ error);
+ }
+ }
+
+ private LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>();
+
+ @Override
+ public void onStarted() {
+ mCallbacks.add(new CallbackValue(CallbackType.ON_STARTED));
+ }
+
+ @Override
+ public void onStopped() {
+ mCallbacks.add(new CallbackValue(CallbackType.ON_STOPPED));
+ }
+
+ @Override
+ public void onError(int error) {
+ mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, error));
+ }
+
+ private void expectCallback(CallbackValue callbackValue) {
+ try {
+ assertEquals(
+ callbackValue,
+ mCallbacks.poll(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } catch (InterruptedException e) {
+ fail(callbackValue.callbackType + " callback not seen after " + TIMEOUT_MS + " ms");
+ }
+ }
+
+ public void expectStarted() {
+ expectCallback(new CallbackValue(CallbackType.ON_STARTED));
+ }
+
+ public void expectStopped() {
+ expectCallback(new CallbackValue(CallbackType.ON_STOPPED));
+ }
+
+ public void expectError(int error) {
+ expectCallback(new CallbackValue(CallbackType.ON_ERROR, error));
+ }
+ }
+
private Network connectKeepaliveNetwork(LinkProperties lp) {
// Ensure the network is disconnected before we do anything.
if (mWiFiNetworkAgent != null) {
@@ -3695,6 +3774,145 @@
}
@Test
+ public void testNattSocketKeepalives() throws Exception {
+ // TODO: 1. Move this outside of ConnectivityServiceTest.
+ // 2. Add helper function to test against newSingleThreadExecutor as well as inline
+ // executor.
+ // 3. Make test to verify that Nat-T keepalive socket is created by IpSecService.
+ final int srcPort = 12345;
+ final InetAddress myIPv4 = InetAddress.getByName("192.0.2.129");
+ final InetAddress notMyIPv4 = InetAddress.getByName("192.0.2.35");
+ final InetAddress myIPv6 = InetAddress.getByName("2001:db8::1");
+ final InetAddress dstIPv4 = InetAddress.getByName("8.8.8.8");
+ final InetAddress dstIPv6 = InetAddress.getByName("2001:4860:4860::8888");
+
+ final int validKaInterval = 15;
+ final int invalidKaInterval = 9;
+
+ final IpSecManager mIpSec = (IpSecManager) mContext.getSystemService(Context.IPSEC_SERVICE);
+ final UdpEncapsulationSocket testSocket = mIpSec.openUdpEncapsulationSocket(srcPort);
+
+ final Executor executor = Executors.newSingleThreadExecutor();
+
+ LinkProperties lp = new LinkProperties();
+ lp.setInterfaceName("wlan12");
+ lp.addLinkAddress(new LinkAddress(myIPv6, 64));
+ lp.addLinkAddress(new LinkAddress(myIPv4, 25));
+ lp.addRoute(new RouteInfo(InetAddress.getByName("fe80::1234")));
+ lp.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254")));
+
+ Network notMyNet = new Network(61234);
+ Network myNet = connectKeepaliveNetwork(lp);
+
+ TestSocketKeepaliveCallback callback = new TestSocketKeepaliveCallback();
+ SocketKeepalive ka;
+
+ // Attempt to start keepalives with invalid parameters and check for errors.
+ // Invalid network.
+ ka = mCm.createSocketKeepalive(notMyNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK);
+
+ // Invalid interval.
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(invalidKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_INTERVAL);
+
+ // Invalid destination.
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv6, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+
+ // Invalid source;
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv6, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+
+ // NAT-T is only supported for IPv4.
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv6, dstIPv6, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+
+ // Sanity check before testing started keepalive.
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectError(SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED);
+
+ // Check that a started keepalive can be stopped.
+ mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS);
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ mWiFiNetworkAgent.setStopKeepaliveError(SocketKeepalive.SUCCESS);
+ ka.stop();
+ callback.expectStopped();
+
+ // Check that deleting the IP address stops the keepalive.
+ LinkProperties bogusLp = new LinkProperties(lp);
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ bogusLp.removeLinkAddress(new LinkAddress(myIPv4, 25));
+ bogusLp.addLinkAddress(new LinkAddress(notMyIPv4, 25));
+ mWiFiNetworkAgent.sendLinkProperties(bogusLp);
+ callback.expectError(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+ mWiFiNetworkAgent.sendLinkProperties(lp);
+
+ // Check that a started keepalive is stopped correctly when the network disconnects.
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ mWiFiNetworkAgent.disconnect();
+ waitFor(mWiFiNetworkAgent.getDisconnectedCV());
+ callback.expectError(SocketKeepalive.ERROR_INVALID_NETWORK);
+
+ // ... and that stopping it after that has no adverse effects.
+ waitForIdle();
+ final Network myNetAlias = myNet;
+ assertNull(mCm.getNetworkCapabilities(myNetAlias));
+ ka.stop();
+
+ // Reconnect.
+ myNet = connectKeepaliveNetwork(lp);
+ mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS);
+
+ // Check things work as expected when the keepalive is stopped and the network disconnects.
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectStarted();
+ ka.stop();
+ mWiFiNetworkAgent.disconnect();
+ waitFor(mWiFiNetworkAgent.getDisconnectedCV());
+ waitForIdle();
+ callback.expectStopped();
+
+ // Reconnect.
+ myNet = connectKeepaliveNetwork(lp);
+ mWiFiNetworkAgent.setStartKeepaliveError(SocketKeepalive.SUCCESS);
+
+ // Check that keepalive slots start from 1 and increment. The first one gets slot 1.
+ mWiFiNetworkAgent.setExpectedKeepaliveSlot(1);
+ ka = mCm.createSocketKeepalive(myNet, testSocket, myIPv4, dstIPv4, executor, callback);
+ ka.start(validKaInterval);
+ callback.expectStarted();
+
+ // The second one gets slot 2.
+ mWiFiNetworkAgent.setExpectedKeepaliveSlot(2);
+ final UdpEncapsulationSocket testSocket2 = mIpSec.openUdpEncapsulationSocket(6789);
+ TestSocketKeepaliveCallback callback2 = new TestSocketKeepaliveCallback();
+ SocketKeepalive ka2 =
+ mCm.createSocketKeepalive(myNet, testSocket2, myIPv4, dstIPv4, executor, callback2);
+ ka2.start(validKaInterval);
+ callback2.expectStarted();
+
+ ka.stop();
+ callback.expectStopped();
+
+ ka2.stop();
+ callback2.expectStopped();
+ }
+
+ @Test
public void testGetCaptivePortalServerUrl() throws Exception {
String url = mCm.getCaptivePortalServerUrl();
assertEquals("http://connectivitycheck.gstatic.com/generate_204", url);
diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTest.java b/tests/utils/testutils/java/com/android/test/filters/SelectTest.java
new file mode 100644
index 0000000..d0350af
--- /dev/null
+++ b/tests/utils/testutils/java/com/android/test/filters/SelectTest.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.filters;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.junit.runner.Description;
+import org.junit.runner.manipulation.Filter;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.StringJoiner;
+
+/**
+ * JUnit filter to select tests.
+ *
+ * <p>This filter selects tests specified by package name, class name, and method name. With this
+ * filter, the package and the class options of AndroidJUnitRunner can be superseded. Also the
+ * restriction that prevents using the package and the class options can be mitigated.
+ *
+ * <p><b>Select out tests from Java packages:</b> this option supersedes {@code -e package} option.
+ * <pre>
+ * adb shell am instrument -w \
+ * -e filter com.android.test.filters.SelectTest \
+ * -e selectTest package1.,package2. \
+ * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ * Note that the ending {@code .} in package name is mandatory.
+ *
+ * <p><b>Select out test classes:</b> this option supersedes {@code -e class} option.
+ * <pre>
+ * adb shell am instrument -w \
+ * -e filter com.android.test.filters.SelectTest \
+ * -e selectTest package1.ClassA,package2.ClassB \
+ * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ *
+ * <p><b>Select out test methods from Java classes:</b>
+ * <pre>
+ * adb shell am instrument -w \
+ * -e filter com.android.test.filters.SelectTest \
+ * -e selectTest package1.ClassA#methodX,package2.ClassB#methodY \
+ * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ *
+ * Those options can be used simultaneously. For example
+ * <pre>
+ * adb shell am instrument -w \
+ * -e filter com.android.test.filters.SelectTest \
+ * -e selectTest package1.,package2.classA,package3.ClassB#methodZ \
+ * com.tests.pkg/androidx.test.runner.AndroidJUnitRunner
+ * </pre>
+ * will select out all tests in package1, all tests in classA, and ClassB#methodZ test.
+ *
+ * <p>Note that when this option is specified with either {@code -e package} or {@code -e class}
+ * option, filtering behaves as logically conjunction. Other options, such as {@code -e notPackage},
+ * {@code -e notClass}, {@code -e annotation}, and {@code -e notAnnotation}, should work as expected
+ * with this SelectTest option.
+ *
+ * <p>When specified with {@code -e selectTest_verbose true} option, {@link SelectTest} verbosely
+ * logs to logcat while parsing {@code -e selectTest} option.
+ */
+public class SelectTest extends Filter {
+
+ private static final String TAG = SelectTest.class.getSimpleName();
+
+ @VisibleForTesting
+ static final String OPTION_SELECT_TEST = "selectTest";
+ @VisibleForTesting
+ static final String OPTION_SELECT_TEST_VERBOSE = OPTION_SELECT_TEST + "_verbose";
+
+ private static final String ARGUMENT_ITEM_SEPARATOR = ",";
+ private static final String PACKAGE_NAME_SEPARATOR = ".";
+ private static final String METHOD_SEPARATOR = "#";
+
+ @Nullable
+ private final PackageSet mPackageSet;
+
+ /**
+ * Construct {@link SelectTest} filter from instrumentation arguments in {@link Bundle}.
+ *
+ * @param testArgs instrumentation test arguments.
+ */
+ public SelectTest(@NonNull Bundle testArgs) {
+ mPackageSet = parseSelectTest(testArgs);
+ }
+
+ @Override
+ public boolean shouldRun(Description description) {
+ if (mPackageSet == null) {
+ // Accept all tests because this filter is disabled.
+ return true;
+ }
+ String testClassName = description.getClassName();
+ String testMethodName = description.getMethodName();
+ return mPackageSet.accept(testClassName, testMethodName);
+ }
+
+ @Override
+ public String describe() {
+ return OPTION_SELECT_TEST + "=" + mPackageSet;
+ }
+
+ /**
+ * Create {@link #OPTION_SELECT_TEST} argument and add it to {@code testArgs}.
+ *
+ * <p>This method is intended to be used at constructor of extended {@link Filter} class.
+ *
+ * @param testArgs instrumentation test arguments.
+ * @param selectTests array of class name to be selected to run.
+ * @return modified instrumentation test arguments.
+ */
+ @NonNull
+ protected static Bundle addSelectTest(
+ @NonNull Bundle testArgs, @NonNull String... selectTests) {
+ if (selectTests.length == 0) {
+ return testArgs;
+ }
+ testArgs.putString(OPTION_SELECT_TEST, join(Arrays.asList(selectTests)));
+ return testArgs;
+ }
+
+ /**
+ * Parse {@code -e selectTest} argument.
+ * @param testArgs instrumentation test arguments.
+ * @return {@link PackageSet} that will filter tests. Returns {@code null} when no
+ * {@code -e selectTest} option is specified, thus this filter gets disabled.
+ */
+ @Nullable
+ private static PackageSet parseSelectTest(Bundle testArgs) {
+ final String selectTestArgs = testArgs.getString(OPTION_SELECT_TEST);
+ if (selectTestArgs == null) {
+ Log.w(TAG, "Disabled because no " + OPTION_SELECT_TEST + " option specified");
+ return null;
+ }
+
+ final boolean verbose = new Boolean(testArgs.getString(OPTION_SELECT_TEST_VERBOSE));
+ final PackageSet packageSet = new PackageSet(verbose);
+ for (String selectTestArg : selectTestArgs.split(ARGUMENT_ITEM_SEPARATOR)) {
+ packageSet.add(selectTestArg);
+ }
+ return packageSet;
+ }
+
+ private static String getPackageName(String selectTestArg) {
+ int endPackagePos = selectTestArg.lastIndexOf(PACKAGE_NAME_SEPARATOR);
+ return (endPackagePos < 0) ? "" : selectTestArg.substring(0, endPackagePos);
+ }
+
+ @Nullable
+ private static String getClassName(String selectTestArg) {
+ if (selectTestArg.endsWith(PACKAGE_NAME_SEPARATOR)) {
+ return null;
+ }
+ int methodSepPos = selectTestArg.indexOf(METHOD_SEPARATOR);
+ return (methodSepPos < 0) ? selectTestArg : selectTestArg.substring(0, methodSepPos);
+ }
+
+ @Nullable
+ private static String getMethodName(String selectTestArg) {
+ int methodSepPos = selectTestArg.indexOf(METHOD_SEPARATOR);
+ return (methodSepPos < 0) ? null : selectTestArg.substring(methodSepPos + 1);
+ }
+
+ /** Package level filter */
+ private static class PackageSet {
+ private final boolean mVerbose;
+ /**
+ * Java package name to {@link ClassSet} map. To represent package filtering, a map value
+ * can be {@code null}.
+ */
+ private final Map<String, ClassSet> mClassSetMap = new LinkedHashMap<>();
+
+ PackageSet(boolean verbose) {
+ mVerbose = verbose;
+ }
+
+ void add(final String selectTestArg) {
+ final String packageName = getPackageName(selectTestArg);
+ final String className = getClassName(selectTestArg);
+
+ if (className == null) {
+ ClassSet classSet = mClassSetMap.put(packageName, null); // package filtering.
+ if (mVerbose) {
+ logging("Select package " + selectTestArg, classSet != null,
+ "; supersede " + classSet);
+ }
+ return;
+ }
+
+ ClassSet classSet = mClassSetMap.get(packageName);
+ if (classSet == null) {
+ if (mClassSetMap.containsKey(packageName)) {
+ if (mVerbose) {
+ logging("Select package " + packageName + PACKAGE_NAME_SEPARATOR, true,
+ " ignore " + selectTestArg);
+ }
+ return;
+ }
+ classSet = new ClassSet(mVerbose);
+ mClassSetMap.put(packageName, classSet);
+ }
+ classSet.add(selectTestArg);
+ }
+
+ boolean accept(String className, @Nullable String methodName) {
+ String packageName = getPackageName(className);
+ if (!mClassSetMap.containsKey(packageName)) {
+ return false;
+ }
+ ClassSet classSet = mClassSetMap.get(packageName);
+ return classSet == null || classSet.accept(className, methodName);
+ }
+
+ @Override
+ public String toString() {
+ StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
+ for (String packageName : mClassSetMap.keySet()) {
+ ClassSet classSet = mClassSetMap.get(packageName);
+ joiner.add(classSet == null
+ ? packageName + PACKAGE_NAME_SEPARATOR : classSet.toString());
+ }
+ return joiner.toString();
+ }
+ }
+
+ /** Class level filter */
+ private static class ClassSet {
+ private final boolean mVerbose;
+ /**
+ * Java class name to set of method names map. To represent class filtering, a map value
+ * can be {@code null}.
+ */
+ private final Map<String, Set<String>> mMethodSetMap = new LinkedHashMap<>();
+
+ ClassSet(boolean verbose) {
+ mVerbose = verbose;
+ }
+
+ void add(String selectTestArg) {
+ final String className = getClassName(selectTestArg);
+ final String methodName = getMethodName(selectTestArg);
+
+ if (methodName == null) {
+ Set<String> methodSet = mMethodSetMap.put(className, null); // class filtering.
+ if (mVerbose) {
+ logging("Select class " + selectTestArg, methodSet != null,
+ "; supersede " + toString(className, methodSet));
+ }
+ return;
+ }
+
+ Set<String> methodSet = mMethodSetMap.get(className);
+ if (methodSet == null) {
+ if (mMethodSetMap.containsKey(className)) {
+ if (mVerbose) {
+ logging("Select class " + className, true, "; ignore " + selectTestArg);
+ }
+ return;
+ }
+ methodSet = new LinkedHashSet<>();
+ mMethodSetMap.put(className, methodSet);
+ }
+
+ methodSet.add(methodName);
+ if (mVerbose) {
+ logging("Select method " + selectTestArg, false, null);
+ }
+ }
+
+ boolean accept(String className, @Nullable String methodName) {
+ if (!mMethodSetMap.containsKey(className)) {
+ return false;
+ }
+ Set<String> methodSet = mMethodSetMap.get(className);
+ return methodName == null || methodSet == null || methodSet.contains(methodName);
+ }
+
+ @Override
+ public String toString() {
+ StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
+ for (String className : mMethodSetMap.keySet()) {
+ joiner.add(toString(className, mMethodSetMap.get(className)));
+ }
+ return joiner.toString();
+ }
+
+ private static String toString(String className, @Nullable Set<String> methodSet) {
+ if (methodSet == null) {
+ return className;
+ }
+ StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
+ for (String methodName : methodSet) {
+ joiner.add(className + METHOD_SEPARATOR + methodName);
+ }
+ return joiner.toString();
+ }
+ }
+
+ private static void logging(String infoLog, boolean isWarning, String warningLog) {
+ if (isWarning) {
+ Log.w(TAG, infoLog + warningLog);
+ } else {
+ Log.i(TAG, infoLog);
+ }
+ }
+
+ private static String join(Collection<String> list) {
+ StringJoiner joiner = new StringJoiner(ARGUMENT_ITEM_SEPARATOR);
+ for (String text : list) {
+ joiner.add(text);
+ }
+ return joiner.toString();
+ }
+}
diff --git a/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java
new file mode 100644
index 0000000..163b00a
--- /dev/null
+++ b/tests/utils/testutils/java/com/android/test/filters/SelectTestTests.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.test.filters;
+
+import static com.android.test.filters.SelectTest.OPTION_SELECT_TEST;
+import static com.android.test.filters.SelectTest.OPTION_SELECT_TEST_VERBOSE;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Bundle;
+import android.util.ArraySet;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.Description;
+import org.junit.runner.manipulation.Filter;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.StringJoiner;
+
+public class SelectTestTests {
+
+ private static final String PACKAGE_A = "packageA.";
+ private static final String PACKAGE_B = "packageB.";
+ private static final String PACKAGE_C = "packageC.";
+ private static final String CLASS_A1 = PACKAGE_A + "Class1";
+ private static final String CLASS_A2 = PACKAGE_A + "Class2";
+ private static final String CLASS_B3 = PACKAGE_B + "Class3";
+ private static final String CLASS_B4 = PACKAGE_B + "Class4";
+ private static final String CLASS_C5 = PACKAGE_C + "Class5";
+ private static final String CLASS_C6 = PACKAGE_C + "Class6";
+ private static final String METHOD_A1K = CLASS_A1 + "#methodK";
+ private static final String METHOD_A1L = CLASS_A1 + "#methodL";
+ private static final String METHOD_A2M = CLASS_A2 + "#methodM";
+ private static final String METHOD_A2N = CLASS_A2 + "#methodN";
+ private static final String METHOD_B3P = CLASS_B3 + "#methodP";
+ private static final String METHOD_B3Q = CLASS_B3 + "#methodQ";
+ private static final String METHOD_B4R = CLASS_B4 + "#methodR";
+ private static final String METHOD_B4S = CLASS_B4 + "#methodS";
+ private static final String METHOD_C5W = CLASS_C5 + "#methodW";
+ private static final String METHOD_C5X = CLASS_C5 + "#methodX";
+ private static final String METHOD_C6Y = CLASS_C6 + "#methodY";
+ private static final String METHOD_C6Z = CLASS_C6 + "#methodZ";
+
+ private static final Set<Description> TEST_METHOD_A1K = methodTest(METHOD_A1K);
+ private static final Set<Description> TEST_METHOD_A1L = methodTest(METHOD_A1L);
+ private static final Set<Description> TEST_METHOD_A2M = methodTest(METHOD_A2M);
+ private static final Set<Description> TEST_METHOD_A2N = methodTest(METHOD_A2N);
+ private static final Set<Description> TEST_METHOD_B3P = methodTest(METHOD_B3P);
+ private static final Set<Description> TEST_METHOD_B3Q = methodTest(METHOD_B3Q);
+ private static final Set<Description> TEST_METHOD_B4R = methodTest(METHOD_B4R);
+ private static final Set<Description> TEST_METHOD_B4S = methodTest(METHOD_B4S);
+ private static final Set<Description> TEST_METHOD_C5W = methodTest(METHOD_C5W);
+ private static final Set<Description> TEST_METHOD_C5X = methodTest(METHOD_C5X);
+ private static final Set<Description> TEST_METHOD_C6Y = methodTest(METHOD_C6Y);
+ private static final Set<Description> TEST_METHOD_C6Z = methodTest(METHOD_C6Z);
+ private static final Set<Description> TEST_CLASS_A1 = merge(TEST_METHOD_A1K, TEST_METHOD_A1L);
+ private static final Set<Description> TEST_CLASS_A2 = merge(TEST_METHOD_A2M, TEST_METHOD_A2N);
+ private static final Set<Description> TEST_CLASS_B3 = merge(TEST_METHOD_B3P, TEST_METHOD_B3Q);
+ private static final Set<Description> TEST_CLASS_B4 = merge(TEST_METHOD_B4R, TEST_METHOD_B4S);
+ private static final Set<Description> TEST_CLASS_C5 = merge(TEST_METHOD_C5W, TEST_METHOD_C5X);
+ private static final Set<Description> TEST_CLASS_C6 = merge(TEST_METHOD_C6Y, TEST_METHOD_C6Z);
+ private static final Set<Description> TEST_PACKAGE_A = merge(TEST_CLASS_A1, TEST_CLASS_A2);
+ private static final Set<Description> TEST_PACKAGE_B = merge(TEST_CLASS_B3, TEST_CLASS_B4);
+ private static final Set<Description> TEST_PACKAGE_C = merge(TEST_CLASS_C5, TEST_CLASS_C6);
+ private static final Set<Description> TEST_ALL =
+ merge(TEST_PACKAGE_A, TEST_PACKAGE_B, TEST_PACKAGE_C);
+
+ private SelectTestBuilder mBuilder;
+
+ @Before
+ public void setUp() {
+ mBuilder = new SelectTestBuilder();
+ }
+
+ private static class SelectTestBuilder {
+ private final Bundle mTestArgs = new Bundle();
+
+ Filter build() {
+ mTestArgs.putString(OPTION_SELECT_TEST_VERBOSE, Boolean.TRUE.toString());
+ return new SelectTest(mTestArgs);
+ }
+
+ SelectTestBuilder withSelectTest(String... selectTestArgs) {
+ putTestOption(OPTION_SELECT_TEST, selectTestArgs);
+ return this;
+ }
+
+ private void putTestOption(String option, String... args) {
+ if (args.length > 0) {
+ StringJoiner joiner = new StringJoiner(",");
+ for (String arg : args) {
+ joiner.add(arg);
+ }
+ mTestArgs.putString(option, joiner.toString());
+ }
+ }
+ }
+
+ private static Set<Description> methodTest(String testName) {
+ int methodSep = testName.indexOf("#");
+ String className = testName.substring(0, methodSep);
+ String methodName = testName.substring(methodSep + 1);
+ final Set<Description> tests = new ArraySet<>();
+ tests.add(Description.createSuiteDescription(className));
+ tests.add(Description.createTestDescription(className, methodName));
+ return Collections.unmodifiableSet(tests);
+ }
+
+ @SafeVarargs
+ private static Set<Description> merge(Set<Description>... testSpecs) {
+ final Set<Description> merged = new LinkedHashSet<>();
+ for (Set<Description> testSet : testSpecs) {
+ merged.addAll(testSet);
+ }
+ return Collections.unmodifiableSet(merged);
+ }
+
+ @SafeVarargs
+ private static void acceptTests(Filter filter, Set<Description>... testSpecs) {
+ final Set<Description> accepts = merge(testSpecs);
+ for (Description test : TEST_ALL) {
+ if (accepts.contains(test)) {
+ assertTrue("accept " + test, filter.shouldRun(test));
+ } else {
+ assertFalse("reject " + test, filter.shouldRun(test));
+ }
+ }
+ }
+
+ @Test
+ public void testFilterDisabled() {
+ final Filter filter = mBuilder.build();
+ acceptTests(filter, TEST_ALL);
+ }
+
+ @Test
+ public void testSelectPackage() {
+ final Filter filter = mBuilder.withSelectTest(PACKAGE_A, PACKAGE_B).build();
+ acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_B);
+ }
+
+ @Test
+ public void testSelectClass() {
+ final Filter filter = mBuilder.withSelectTest(CLASS_A1, CLASS_A2, CLASS_B3).build();
+ acceptTests(filter, TEST_CLASS_A1, TEST_CLASS_A2, TEST_CLASS_B3);
+ }
+
+ @Test
+ public void testSelectMethod() {
+ final Filter filter = mBuilder
+ .withSelectTest(METHOD_A1K, METHOD_A2M, METHOD_A2N, METHOD_B3P).build();
+ acceptTests(filter, TEST_METHOD_A1K, TEST_METHOD_A2M, TEST_METHOD_A2N, TEST_METHOD_B3P);
+ }
+
+ @Test
+ public void testSelectClassAndPackage() {
+ final Filter filter = mBuilder.withSelectTest(CLASS_A1, PACKAGE_B, CLASS_C5).build();
+ acceptTests(filter, TEST_CLASS_A1, TEST_PACKAGE_B, TEST_CLASS_C5);
+ }
+
+ @Test
+ public void testSelectMethodAndPackage() {
+ final Filter filter = mBuilder.withSelectTest(METHOD_A1K, PACKAGE_B, METHOD_C5W).build();
+ acceptTests(filter, TEST_METHOD_A1K, TEST_PACKAGE_B, TEST_METHOD_C5W);
+ }
+
+ @Test
+ public void testSelectMethodAndClass() {
+ final Filter filter = mBuilder.withSelectTest(METHOD_A1K, CLASS_C5, METHOD_B3P).build();
+ acceptTests(filter, TEST_METHOD_A1K, TEST_CLASS_C5, TEST_METHOD_B3P);
+ }
+
+ @Test
+ public void testSelectClassAndSamePackage() {
+ final Filter filter = mBuilder.withSelectTest(
+ CLASS_A1, PACKAGE_A, CLASS_B3, PACKAGE_C, CLASS_C5).build();
+ acceptTests(filter, TEST_PACKAGE_A, TEST_CLASS_B3, TEST_PACKAGE_C);
+ }
+
+ @Test
+ public void testSelectMethodAndSameClass() {
+ final Filter filter = mBuilder.withSelectTest(
+ METHOD_A1K, METHOD_A2M, CLASS_A1, CLASS_B3, METHOD_B3P, METHOD_B4R).build();
+ acceptTests(filter, TEST_CLASS_A1, TEST_METHOD_A2M, TEST_CLASS_B3, TEST_METHOD_B4R);
+ }
+
+ @Test
+ public void testSelectMethodAndSamePackage() {
+ final Filter filter = mBuilder.withSelectTest(
+ METHOD_A1K, METHOD_A1L, METHOD_A2M, PACKAGE_A,
+ PACKAGE_C, METHOD_C5W, METHOD_C5X, METHOD_C6Y).build();
+ acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_C);
+ }
+
+ @Test
+ public void testSelectMethodAndClassAndPackage() {
+ final Filter filter = mBuilder.withSelectTest(
+ METHOD_A1K, CLASS_A1, METHOD_A1L, METHOD_A2M, PACKAGE_A,
+ PACKAGE_B, METHOD_B3Q, CLASS_B3, METHOD_B4R, METHOD_B3P).build();
+ acceptTests(filter, TEST_PACKAGE_A, TEST_PACKAGE_B);
+ }
+}