Merge "Add API for NetworkStack to start captive portal"
diff --git a/Android.bp b/Android.bp
index fcea5d0..8e17479 100644
--- a/Android.bp
+++ b/Android.bp
@@ -104,7 +104,6 @@
"core/java/android/app/timedetector/ITimeDetectorService.aidl",
"core/java/android/app/timezone/ICallback.aidl",
"core/java/android/app/timezone/IRulesManager.aidl",
- "core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl",
"core/java/android/app/usage/ICacheQuotaService.aidl",
"core/java/android/app/usage/IStorageStatsManager.aidl",
"core/java/android/app/usage/IUsageStatsManager.aidl",
@@ -833,6 +832,7 @@
"core/java/android/net/ProxyInfoParcelable.aidl",
"core/java/android/net/RouteInfoParcelable.aidl",
"core/java/android/net/StaticIpConfigurationParcelable.aidl",
+ "core/java/android/net/TcpKeepalivePacketDataParcelable.aidl",
"core/java/android/net/dhcp/DhcpServingParamsParcel.aidl",
"core/java/android/net/dhcp/IDhcpServer.aidl",
"core/java/android/net/dhcp/IDhcpServerCallbacks.aidl",
diff --git a/api/current.txt b/api/current.txt
index b885bbd..d787070 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -27167,7 +27167,7 @@
field public static final String EXTRA_NETWORK = "android.net.extra.NETWORK";
field @Deprecated public static final String EXTRA_NETWORK_INFO = "networkInfo";
field public static final String EXTRA_NETWORK_REQUEST = "android.net.extra.NETWORK_REQUEST";
- field public static final String EXTRA_NETWORK_TYPE = "networkType";
+ field @Deprecated public static final String EXTRA_NETWORK_TYPE = "networkType";
field public static final String EXTRA_NO_CONNECTIVITY = "noConnectivity";
field @Deprecated public static final String EXTRA_OTHER_NETWORK_INFO = "otherNetwork";
field public static final String EXTRA_REASON = "reason";
@@ -40410,6 +40410,7 @@
method public static java.io.FileDescriptor accept(java.io.FileDescriptor, java.net.InetSocketAddress) throws android.system.ErrnoException, java.net.SocketException;
method public static boolean access(String, int) throws android.system.ErrnoException;
method public static void bind(java.io.FileDescriptor, java.net.InetAddress, int) throws android.system.ErrnoException, java.net.SocketException;
+ method public static void bind(java.io.FileDescriptor, java.net.SocketAddress) throws android.system.ErrnoException, java.net.SocketException;
method public static void chmod(String, int) throws android.system.ErrnoException;
method public static void chown(String, int, int) throws android.system.ErrnoException;
method public static void close(java.io.FileDescriptor) throws android.system.ErrnoException;
@@ -40478,6 +40479,7 @@
method public static long sendfile(java.io.FileDescriptor, java.io.FileDescriptor, android.system.Int64Ref, long) throws android.system.ErrnoException;
method public static int sendto(java.io.FileDescriptor, java.nio.ByteBuffer, int, java.net.InetAddress, int) throws android.system.ErrnoException, java.net.SocketException;
method public static int sendto(java.io.FileDescriptor, byte[], int, int, int, java.net.InetAddress, int) throws android.system.ErrnoException, java.net.SocketException;
+ method public static int sendto(java.io.FileDescriptor, byte[], int, int, int, java.net.SocketAddress) throws android.system.ErrnoException, java.net.SocketException;
method @Deprecated public static void setegid(int) throws android.system.ErrnoException;
method public static void setenv(String, String, boolean) throws android.system.ErrnoException;
method @Deprecated public static void seteuid(int) throws android.system.ErrnoException;
diff --git a/api/system-current.txt b/api/system-current.txt
index 6cb974f..981789c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3069,6 +3069,7 @@
public class ConnectivityManager {
method @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createNattKeepalive(@NonNull android.net.Network, @NonNull java.io.FileDescriptor, @NonNull java.net.InetAddress, @NonNull java.net.InetAddress, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
+ method @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD) public android.net.SocketKeepalive createSocketKeepalive(@NonNull android.net.Network, @NonNull java.net.Socket, @NonNull java.util.concurrent.Executor, @NonNull android.net.SocketKeepalive.Callback);
method public boolean getAvoidBadWifi();
method @RequiresPermission(android.Manifest.permission.LOCAL_MAC_ADDRESS) public String getCaptivePortalServerUrl();
method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementValue(int, boolean, @NonNull android.net.ConnectivityManager.TetheringEntitlementValueListener, @Nullable android.os.Handler);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 5fd148e..3da5e0c 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -161,6 +161,7 @@
BluetoothBondStateChanged bluetooth_bond_state_changed = 165;
BluetoothClassicPairingEventReported bluetooth_classic_pairing_event_reported = 166;
BluetoothSmpPairingEventReported bluetooth_smp_pairing_event_reported = 167;
+ BluetoothSocketConnectionStateChanged bluetooth_socket_connection_state_changed = 171;
}
// Pulled events will start at field 10000.
@@ -1719,6 +1720,36 @@
}
/**
+ * Logs when a Bluetooth socket’s connection state changed
+ *
+ * Logged from:
+ * system/bt
+ */
+message BluetoothSocketConnectionStateChanged {
+ // An identifier that can be used to match events for this device.
+ // Currently, this is a salted hash of the MAC address of this Bluetooth device.
+ // Salt: Randomly generated 256 bit value
+ // Hash algorithm: HMAC-SHA256
+ // Size: 32 byte
+ // Default: null or empty if the device identifier is not known
+ optional bytes obfuscated_id = 1 [(android.os.statsd.log_mode) = MODE_BYTES];
+ // Port of this socket
+ // Default 0 when unknown or don't care
+ optional int32 port = 2;
+ // Socket type as mentioned in
+ // frameworks/base/core/java/android/bluetooth/BluetoothSocket.java
+ // Default: SOCKET_TYPE_UNKNOWN
+ optional android.bluetooth.SocketTypeEnum type = 3;
+ // Socket connection state
+ // Default: SOCKET_CONNECTION_STATE_UNKNOWN
+ optional android.bluetooth.SocketConnectionstateEnum state = 4;
+ // Number of bytes sent to remote device during this connection
+ optional int64 tx_bytes = 5;
+ // Number of bytes received from remote device during this connection
+ optional int64 rx_bytes = 6;
+}
+
+/**
* Logs when something is plugged into or removed from the USB-C connector.
*
* Logged from:
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index a8fc9d24..c42a2bc 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -26,7 +26,6 @@
import android.app.slice.SliceManager;
import android.app.timedetector.TimeDetector;
import android.app.timezone.RulesManager;
-import android.app.timezonedetector.TimeZoneDetector;
import android.app.trust.TrustManager;
import android.app.usage.IStorageStatsManager;
import android.app.usage.IUsageStatsManager;
@@ -1060,13 +1059,6 @@
throws ServiceNotFoundException {
return new TimeDetector();
}});
- registerService(Context.TIME_ZONE_DETECTOR_SERVICE, TimeZoneDetector.class,
- new CachedServiceFetcher<TimeZoneDetector>() {
- @Override
- public TimeZoneDetector createService(ContextImpl ctx)
- throws ServiceNotFoundException {
- return new TimeZoneDetector();
- }});
registerService(Context.DYNAMIC_ANDROID_SERVICE, DynamicAndroidManager.class,
new CachedServiceFetcher<DynamicAndroidManager>() {
@Override
diff --git a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl b/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
deleted file mode 100644
index ef2cbab..0000000
--- a/core/java/android/app/timezonedetector/ITimeZoneDetectorService.aidl
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.timezonedetector;
-
-/**
- * System private API to comunicate with time zone detector service.
- *
- * <p>Used by parts of the Android system with signals associated with the device's time zone to
- * provide information to the Time Zone Detector Service.
- *
- * <p>Use the {@link android.app.timezonedetector.TimeZoneDetector} class rather than going through
- * this Binder interface directly. See {@link android.app.timezonedetector.TimeZoneDetectorService}
- * for more complete documentation.
- *
- *
- * {@hide}
- */
-interface ITimeZoneDetectorService {
- void stubbedCall();
-}
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
deleted file mode 100644
index be3c764..0000000
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.app.timezonedetector;
-
-import android.annotation.SystemService;
-import android.content.Context;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.os.ServiceManager.ServiceNotFoundException;
-import android.util.Log;
-
-/**
- * The interface through which system components can send signals to the TimeZoneDetectorService.
- * @hide
- */
-@SystemService(Context.TIME_ZONE_DETECTOR_SERVICE)
-public final class TimeZoneDetector {
-
- private static final String TAG = "timezonedetector.TimeZoneDetector";
- private static final boolean DEBUG = false;
-
- private final ITimeZoneDetectorService mITimeZoneDetectorService;
-
- public TimeZoneDetector() throws ServiceNotFoundException {
- mITimeZoneDetectorService = ITimeZoneDetectorService.Stub.asInterface(
- ServiceManager.getServiceOrThrow(Context.TIME_ZONE_DETECTOR_SERVICE));
-
- }
- /**
- * Does nothing.
- * TODO: Remove this when the service implementation is built out.
- */
- public void stubbedCall() {
- if (DEBUG) {
- Log.d(TAG, "stubbedCall called");
- }
- try {
- mITimeZoneDetectorService.stubbedCall();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index ba6e7ae8..493aac6 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -3087,7 +3087,6 @@
CROSS_PROFILE_APPS_SERVICE,
//@hide: SYSTEM_UPDATE_SERVICE,
//@hide: TIME_DETECTOR_SERVICE,
- //@hide: TIME_ZONE_DETECTOR_SERVICE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ServiceName {}
@@ -4290,15 +4289,6 @@
/**
* Use with {@link #getSystemService(String)} to retrieve an
- * {@link android.app.timezonedetector.ITimeZoneDetectorService}.
- * @hide
- *
- * @see #getSystemService(String)
- */
- public static final String TIME_ZONE_DETECTOR_SERVICE = "time_zone_detector";
-
- /**
- * Use with {@link #getSystemService(String)} to retrieve an
* {@link android.telephony.ims.RcsManager}.
* @hide
*/
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 28af025..68ac46c 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -68,6 +68,7 @@
import java.lang.annotation.RetentionPolicy;
import java.net.InetAddress;
import java.net.InetSocketAddress;
+import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -177,10 +178,10 @@
* The lookup key for a {@link NetworkInfo} object. Retrieve with
* {@link android.content.Intent#getParcelableExtra(String)}.
*
- * @deprecated Since {@link NetworkInfo} can vary based on UID, applications
- * should always obtain network information through
- * {@link #getActiveNetworkInfo()}.
- * @see #EXTRA_NETWORK_TYPE
+ * @deprecated The {@link NetworkInfo} object is deprecated, as many of its properties
+ * can't accurately represent modern network characteristics.
+ * Please obtain information about networks from the {@link NetworkCapabilities}
+ * or {@link LinkProperties} objects instead.
*/
@Deprecated
public static final String EXTRA_NETWORK_INFO = "networkInfo";
@@ -189,7 +190,11 @@
* Network type which triggered a {@link #CONNECTIVITY_ACTION} broadcast.
*
* @see android.content.Intent#getIntExtra(String, int)
+ * @deprecated The network type is not rich enough to represent the characteristics
+ * of modern networks. Please use {@link NetworkCapabilities} instead,
+ * in particular the transports.
*/
+ @Deprecated
public static final String EXTRA_NETWORK_TYPE = "networkType";
/**
@@ -1243,9 +1248,13 @@
* is no current default network.
*
* {@hide}
+ * @deprecated please use {@link #getLinkProperties(Network)} on the return
+ * value of {@link #getActiveNetwork()} instead. In particular,
+ * this method will return non-null LinkProperties even if the
+ * app is blocked by policy from using this network.
*/
@RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 109783091)
public LinkProperties getActiveLinkProperties() {
try {
return mService.getActiveLinkProperties();
@@ -1881,7 +1890,8 @@
* @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.
+ * @return A {@link SocketKeepalive} object that can be used to control the keepalive on the
+ * given socket.
**/
public SocketKeepalive createSocketKeepalive(@NonNull Network network,
@NonNull UdpEncapsulationSocket socket,
@@ -1910,6 +1920,8 @@
* @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 that can be used to control the keepalive on the
+ * given socket.
* @hide
*/
@SystemApi
@@ -1925,6 +1937,34 @@
}
/**
+ * Request that keepalives be started on a TCP socket.
+ * The socket must be established.
+ *
+ * @param network The {@link Network} the socket is on.
+ * @param socket The socket that needs to be kept alive.
+ * @param executor The executor on which callback will be invoked. This implementation assumes
+ * the provided {@link Executor} runs the callbacks in sequence with no
+ * concurrency. Failing this, no guarantee of correctness can be made. It is
+ * the responsibility of the caller to ensure the executor provides this
+ * guarantee. A simple way of creating such an executor is with the standard
+ * tool {@code Executors.newSingleThreadExecutor}.
+ * @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 that can be used to control the keepalive on the
+ * given socket.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.PACKET_KEEPALIVE_OFFLOAD)
+ public SocketKeepalive createSocketKeepalive(@NonNull Network network,
+ @NonNull Socket socket,
+ @NonNull Executor executor,
+ @NonNull Callback callback) {
+ return new TcpSocketKeepalive(mService, network, socket, 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 ea2f6d2..92a5839 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -190,6 +190,9 @@
int intervalSeconds, in Messenger messenger, in IBinder binder, String srcAddr,
String dstAddr);
+ void startTcpKeepalive(in Network network, in FileDescriptor fd, int intervalSeconds,
+ in Messenger messenger, in IBinder binder);
+
void stopKeepalive(in Network network, int slot);
String getCaptivePortalServerUrl();
diff --git a/core/java/android/net/KeepalivePacketData.java b/core/java/android/net/KeepalivePacketData.java
index 16555d8..18726f7 100644
--- a/core/java/android/net/KeepalivePacketData.java
+++ b/core/java/android/net/KeepalivePacketData.java
@@ -96,7 +96,7 @@
out.writeByteArray(mPacket);
}
- private KeepalivePacketData(Parcel in) {
+ protected KeepalivePacketData(Parcel in) {
srcAddress = NetworkUtils.numericToInetAddress(in.readString());
dstAddress = NetworkUtils.numericToInetAddress(in.readString());
srcPort = in.readInt();
diff --git a/core/java/android/net/NattKeepalivePacketData.java b/core/java/android/net/NattKeepalivePacketData.java
index dd2b108..bdb246f 100644
--- a/core/java/android/net/NattKeepalivePacketData.java
+++ b/core/java/android/net/NattKeepalivePacketData.java
@@ -16,6 +16,9 @@
package android.net;
+import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
+import static android.net.SocketKeepalive.ERROR_INVALID_PORT;
+
import android.net.SocketKeepalive.InvalidPacketException;
import android.net.util.IpUtils;
import android.system.OsConstants;
@@ -44,11 +47,11 @@
throws InvalidPacketException {
if (!(srcAddress instanceof Inet4Address) || !(dstAddress instanceof Inet4Address)) {
- throw new InvalidPacketException(SocketKeepalive.ERROR_INVALID_IP_ADDRESS);
+ throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
}
if (dstPort != NattSocketKeepalive.NATT_PORT) {
- throw new InvalidPacketException(SocketKeepalive.ERROR_INVALID_PORT);
+ throw new InvalidPacketException(ERROR_INVALID_PORT);
}
int length = IPV4_HEADER_LENGTH + UDP_HEADER_LENGTH + 1;
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index dfb6d6f..7bef690 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -177,6 +177,26 @@
*/
public static final int EVENT_SOCKET_KEEPALIVE = BASE + 13;
+ // TODO: move the above 2 constants down so they are in order once merge conflicts are resolved
+ /**
+ * Sent by the KeepaliveTracker to NetworkAgent to add a packet filter.
+ *
+ * For TCP keepalive offloads, keepalive packets are sent by the firmware. However, because the
+ * remote site will send ACK packets in response to the keepalive packets, the firmware also
+ * needs to be configured to properly filter the ACKs to prevent the system from waking up.
+ * This does not happen with UDP, so this message is TCP-specific.
+ * arg1 = slot number of the keepalive to filter for.
+ * obj = the keepalive packet to send repeatedly.
+ */
+ public static final int CMD_ADD_KEEPALIVE_PACKET_FILTER = BASE + 16;
+
+ /**
+ * Sent by the KeepaliveTracker to NetworkAgent to remove a packet filter. See
+ * {@link #CMD_ADD_KEEPALIVE_PACKET_FILTER}.
+ * arg1 = slot number of the keepalive packet filter to remove.
+ */
+ public static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER = BASE + 17;
+
/**
* Sent by ConnectivityService to inform this network transport of signal strength thresholds
* that when crossed should trigger a system wakeup and a NetworkCapabilities update.
@@ -312,6 +332,14 @@
preventAutomaticReconnect();
break;
}
+ case CMD_ADD_KEEPALIVE_PACKET_FILTER: {
+ addKeepalivePacketFilter(msg);
+ break;
+ }
+ case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: {
+ removeKeepalivePacketFilter(msg);
+ break;
+ }
}
}
@@ -461,6 +489,24 @@
}
/**
+ * Called by ConnectivityService to add specific packet filter to network hardware to block
+ * ACKs matching the sent keepalive packets. Implementations that support this feature must
+ * override this method.
+ */
+ protected void addKeepalivePacketFilter(Message msg) {
+ onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED);
+ }
+
+ /**
+ * Called by ConnectivityService to remove a packet filter installed with
+ * {@link #addKeepalivePacketFilter(Message)}. Implementations that support this feature
+ * must override this method.
+ */
+ protected void removeKeepalivePacketFilter(Message msg) {
+ onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_HARDWARE_UNSUPPORTED);
+ }
+
+ /**
* Called by ConnectivityService to inform this network transport of signal strength thresholds
* that when crossed should trigger a system wakeup and a NetworkCapabilities update.
*/
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index 07668a9..0ae29b1 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -71,6 +71,18 @@
throws SocketException;
/**
+ * Attaches a socket filter that drops all of incoming packets.
+ * @param fd the socket's {@link FileDescriptor}.
+ */
+ public static native void attachDropAllBPFFilter(FileDescriptor fd) throws SocketException;
+
+ /**
+ * Detaches a socket filter.
+ * @param fd the socket's {@link FileDescriptor}.
+ */
+ public static native void detachBPFFilter(FileDescriptor fd) throws SocketException;
+
+ /**
* Configures a socket for receiving ICMPv6 router solicitations and sending advertisements.
* @param fd the socket's {@link FileDescriptor}.
* @param ifIndex the interface index.
@@ -170,6 +182,16 @@
private static native void addArpEntry(byte[] ethAddr, byte[] netAddr, String ifname,
FileDescriptor fd) throws IOException;
+
+ /**
+ * Get the tcp repair window associated with the {@code fd}.
+ *
+ * @param fd the tcp socket's {@link FileDescriptor}.
+ * @return a {@link TcpRepairWindow} object indicates tcp window size.
+ */
+ public static native TcpRepairWindow getTcpRepairWindow(FileDescriptor fd)
+ throws ErrnoException;
+
/**
* @see Inet4AddressUtils#intToInet4AddressHTL(int)
* @deprecated Use either {@link Inet4AddressUtils#intToInet4AddressHTH(int)}
diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java
index a47c11af..07728be 100644
--- a/core/java/android/net/SocketKeepalive.java
+++ b/core/java/android/net/SocketKeepalive.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
@@ -110,14 +111,42 @@
public static final int MAX_INTERVAL_SEC = 3600;
/**
+ * An exception that embarks an error code.
+ * @hide
+ */
+ public static class ErrorCodeException extends Exception {
+ public final int error;
+ public ErrorCodeException(final int error, final Throwable e) {
+ super(e);
+ this.error = error;
+ }
+ public ErrorCodeException(final int error) {
+ this.error = error;
+ }
+ }
+
+ /**
+ * This socket is invalid.
+ * See the error code for details, and the optional cause.
+ * @hide
+ */
+ public static class InvalidSocketException extends ErrorCodeException {
+ public InvalidSocketException(final int error, final Throwable e) {
+ super(error, e);
+ }
+ public InvalidSocketException(final int error) {
+ super(error);
+ }
+ }
+
+ /**
* This packet is invalid.
* See the error code for details.
* @hide
*/
- public static class InvalidPacketException extends Exception {
- public final int error;
- public InvalidPacketException(int error) {
- this.error = error;
+ public static class InvalidPacketException extends ErrorCodeException {
+ public InvalidPacketException(final int error) {
+ super(error);
}
}
@@ -127,7 +156,7 @@
@NonNull private final SocketKeepalive.Callback mCallback;
@NonNull private final Looper mLooper;
@NonNull final Messenger mMessenger;
- @NonNull Integer mSlot;
+ @Nullable Integer mSlot;
SocketKeepalive(@NonNull IConnectivityManager service, @NonNull Network network,
@NonNull Executor executor, @NonNull Callback callback) {
diff --git a/core/java/android/net/TcpKeepalivePacketData.java b/core/java/android/net/TcpKeepalivePacketData.java
new file mode 100644
index 0000000..f07dfb6
--- /dev/null
+++ b/core/java/android/net/TcpKeepalivePacketData.java
@@ -0,0 +1,234 @@
+/*
+ * 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 static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.SocketKeepalive.InvalidPacketException;
+import android.net.util.IpUtils;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.system.OsConstants;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Objects;
+
+/**
+ * Represents the actual tcp keep alive packets which will be used for hardware offload.
+ * @hide
+ */
+public class TcpKeepalivePacketData extends KeepalivePacketData implements Parcelable {
+ private static final String TAG = "TcpKeepalivePacketData";
+
+ /** TCP sequence number. */
+ public final int tcpSeq;
+
+ /** TCP ACK number. */
+ public final int tcpAck;
+
+ /** TCP RCV window. */
+ public final int tcpWnd;
+
+ /** TCP RCV window scale. */
+ public final int tcpWndScale;
+
+ private static final int IPV4_HEADER_LENGTH = 20;
+ private static final int IPV6_HEADER_LENGTH = 40;
+ private static final int TCP_HEADER_LENGTH = 20;
+
+ // This should only be constructed via static factory methods, such as
+ // tcpKeepalivePacket.
+ private TcpKeepalivePacketData(TcpSocketInfo tcpDetails, byte[] data)
+ throws InvalidPacketException {
+ super(tcpDetails.srcAddress, tcpDetails.srcPort, tcpDetails.dstAddress,
+ tcpDetails.dstPort, data);
+ tcpSeq = tcpDetails.seq;
+ tcpAck = tcpDetails.ack;
+ // In the packet, the window is shifted right by the window scale.
+ tcpWnd = tcpDetails.rcvWnd;
+ tcpWndScale = tcpDetails.rcvWndScale;
+ }
+
+ /**
+ * Factory method to create tcp keepalive packet structure.
+ */
+ public static TcpKeepalivePacketData tcpKeepalivePacket(
+ TcpSocketInfo tcpDetails) throws InvalidPacketException {
+ final byte[] packet;
+ if ((tcpDetails.srcAddress instanceof Inet4Address)
+ && (tcpDetails.dstAddress instanceof Inet4Address)) {
+ packet = buildV4Packet(tcpDetails);
+ } else {
+ // TODO: support ipv6
+ throw new InvalidPacketException(ERROR_INVALID_IP_ADDRESS);
+ }
+
+ return new TcpKeepalivePacketData(tcpDetails, packet);
+ }
+
+ /**
+ * Build ipv4 tcp keepalive packet, not including the link-layer header.
+ */
+ // TODO : if this code is ever moved to the network stack, factorize constants with the ones
+ // over there.
+ private static byte[] buildV4Packet(TcpSocketInfo tcpDetails) {
+ final int length = IPV4_HEADER_LENGTH + TCP_HEADER_LENGTH;
+ ByteBuffer buf = ByteBuffer.allocate(length);
+ buf.order(ByteOrder.BIG_ENDIAN);
+ // IP version and TOS. TODO : fetch this from getsockopt(SOL_IP, IP_TOS)
+ buf.putShort((short) 0x4500);
+ buf.putShort((short) length);
+ buf.putInt(0x4000); // ID, flags=DF, offset
+ // TODO : fetch TTL from getsockopt(SOL_IP, IP_TTL)
+ buf.put((byte) 64);
+ buf.put((byte) OsConstants.IPPROTO_TCP);
+ final int ipChecksumOffset = buf.position();
+ buf.putShort((short) 0); // IP checksum
+ buf.put(tcpDetails.srcAddress.getAddress());
+ buf.put(tcpDetails.dstAddress.getAddress());
+ buf.putShort((short) tcpDetails.srcPort);
+ buf.putShort((short) tcpDetails.dstPort);
+ buf.putInt(tcpDetails.seq); // Sequence Number
+ buf.putInt(tcpDetails.ack); // ACK
+ buf.putShort((short) 0x5010); // TCP length=5, flags=ACK
+ buf.putShort((short) (tcpDetails.rcvWnd >> tcpDetails.rcvWndScale)); // Window size
+ final int tcpChecksumOffset = buf.position();
+ buf.putShort((short) 0); // TCP checksum
+ // URG is not set therefore the urgent pointer is not included
+ buf.putShort(ipChecksumOffset, IpUtils.ipChecksum(buf, 0));
+ buf.putShort(tcpChecksumOffset, IpUtils.tcpChecksum(
+ buf, 0, IPV4_HEADER_LENGTH, TCP_HEADER_LENGTH));
+
+ return buf.array();
+ }
+
+ // TODO: add buildV6Packet.
+
+ /** Represents tcp/ip information. */
+ // TODO: Replace TcpSocketInfo with TcpKeepalivePacketDataParcelable.
+ public static class TcpSocketInfo {
+ public final InetAddress srcAddress;
+ public final InetAddress dstAddress;
+ public final int srcPort;
+ public final int dstPort;
+ public final int seq;
+ public final int ack;
+ public final int rcvWnd;
+ public final int rcvWndScale;
+
+ public TcpSocketInfo(InetAddress sAddr, int sPort, InetAddress dAddr,
+ int dPort, int writeSeq, int readSeq, int rWnd, int rWndScale) {
+ srcAddress = sAddr;
+ dstAddress = dAddr;
+ srcPort = sPort;
+ dstPort = dPort;
+ seq = writeSeq;
+ ack = readSeq;
+ rcvWnd = rWnd;
+ rcvWndScale = rWndScale;
+ }
+ }
+
+ @Override
+ public boolean equals(@Nullable final Object o) {
+ if (!(o instanceof TcpKeepalivePacketData)) return false;
+ final TcpKeepalivePacketData other = (TcpKeepalivePacketData) o;
+ return this.srcAddress.equals(other.srcAddress)
+ && this.dstAddress.equals(other.dstAddress)
+ && this.srcPort == other.srcPort
+ && this.dstPort == other.dstPort
+ && this.tcpAck == other.tcpAck
+ && this.tcpSeq == other.tcpSeq
+ && this.tcpWnd == other.tcpWnd
+ && this.tcpWndScale == other.tcpWndScale;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(srcAddress, dstAddress, srcPort, dstPort, tcpAck, tcpSeq, tcpWnd,
+ tcpWndScale);
+ }
+
+ /* Parcelable Implementation. */
+ /* Note that this object implements parcelable (and needs to keep doing this as it inherits
+ * from a class that does), but should usually be parceled as a stable parcelable using
+ * the toStableParcelable() and fromStableParcelable() methods.
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /** Write to parcel. */
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeInt(tcpSeq);
+ out.writeInt(tcpAck);
+ out.writeInt(tcpWnd);
+ out.writeInt(tcpWndScale);
+ }
+
+ private TcpKeepalivePacketData(Parcel in) {
+ super(in);
+ tcpSeq = in.readInt();
+ tcpAck = in.readInt();
+ tcpWnd = in.readInt();
+ tcpWndScale = in.readInt();
+ }
+
+ /** Parcelable Creator. */
+ public static final Parcelable.Creator<TcpKeepalivePacketData> CREATOR =
+ new Parcelable.Creator<TcpKeepalivePacketData>() {
+ public TcpKeepalivePacketData createFromParcel(Parcel in) {
+ return new TcpKeepalivePacketData(in);
+ }
+
+ public TcpKeepalivePacketData[] newArray(int size) {
+ return new TcpKeepalivePacketData[size];
+ }
+ };
+
+ /**
+ * Convert this TcpKeepalivePacketData to a TcpKeepalivePacketDataParcelable.
+ */
+ @NonNull
+ public TcpKeepalivePacketDataParcelable toStableParcelable() {
+ final TcpKeepalivePacketDataParcelable parcel = new TcpKeepalivePacketDataParcelable();
+ parcel.srcAddress = srcAddress.getAddress();
+ parcel.srcPort = srcPort;
+ parcel.dstAddress = dstAddress.getAddress();
+ parcel.dstPort = dstPort;
+ parcel.seq = tcpSeq;
+ parcel.ack = tcpAck;
+ return parcel;
+ }
+
+ @Override
+ public String toString() {
+ return "saddr: " + srcAddress
+ + " daddr: " + dstAddress
+ + " sport: " + srcPort
+ + " dport: " + dstPort
+ + " seq: " + tcpSeq
+ + " ack: " + tcpAck
+ + " wnd: " + tcpWnd
+ + " wndScale: " + tcpWndScale;
+ }
+}
diff --git a/telephony/java/android/telephony/ims/RcsPart.java b/core/java/android/net/TcpKeepalivePacketDataParcelable.aidl
similarity index 74%
rename from telephony/java/android/telephony/ims/RcsPart.java
rename to core/java/android/net/TcpKeepalivePacketDataParcelable.aidl
index da50173..7329c63 100644
--- a/telephony/java/android/telephony/ims/RcsPart.java
+++ b/core/java/android/net/TcpKeepalivePacketDataParcelable.aidl
@@ -13,13 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package android.telephony.ims;
-import android.os.Parcelable;
+package android.net;
-/**
- * A part of a composite {@link RcsMessage}.
- * @hide - TODO(sahinc) make this public
- */
-public abstract class RcsPart implements Parcelable {
+parcelable TcpKeepalivePacketDataParcelable {
+ byte[] srcAddress;
+ int srcPort;
+ byte[] dstAddress;
+ int dstPort;
+ int seq;
+ int ack;
}
diff --git a/core/java/android/net/TcpRepairWindow.java b/core/java/android/net/TcpRepairWindow.java
new file mode 100644
index 0000000..86034f0
--- /dev/null
+++ b/core/java/android/net/TcpRepairWindow.java
@@ -0,0 +1,45 @@
+/*
+ * 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;
+
+/**
+ * Corresponds to C's {@code struct tcp_repair_window} from
+ * include/uapi/linux/tcp.h
+ *
+ * @hide
+ */
+public final class TcpRepairWindow {
+ public final int sndWl1;
+ public final int sndWnd;
+ public final int maxWindow;
+ public final int rcvWnd;
+ public final int rcvWup;
+ public final int rcvWndScale;
+
+ /**
+ * Constructs an instance with the given field values.
+ */
+ public TcpRepairWindow(final int sndWl1, final int sndWnd, final int maxWindow,
+ final int rcvWnd, final int rcvWup, final int rcvWndScale) {
+ this.sndWl1 = sndWl1;
+ this.sndWnd = sndWnd;
+ this.maxWindow = maxWindow;
+ this.rcvWnd = rcvWnd;
+ this.rcvWup = rcvWup;
+ this.rcvWndScale = rcvWndScale;
+ }
+}
diff --git a/core/java/android/net/TcpSocketKeepalive.java b/core/java/android/net/TcpSocketKeepalive.java
new file mode 100644
index 0000000..8f6ee7b
--- /dev/null
+++ b/core/java/android/net/TcpSocketKeepalive.java
@@ -0,0 +1,78 @@
+/*
+ * 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.NonNull;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.net.Socket;
+import java.util.concurrent.Executor;
+
+/** @hide */
+final class TcpSocketKeepalive extends SocketKeepalive {
+
+ private final Socket mSocket;
+
+ TcpSocketKeepalive(@NonNull IConnectivityManager service,
+ @NonNull Network network,
+ @NonNull Socket socket,
+ @NonNull Executor executor,
+ @NonNull Callback callback) {
+ super(service, network, executor, callback);
+ mSocket = socket;
+ }
+
+ /**
+ * Starts keepalives. {@code mSocket} must be a connected TCP socket.
+ *
+ * - The application must not write to or read from the socket after calling this method, until
+ * onDataReceived, onStopped, or onError are called. If it does, the keepalive will fail
+ * with {@link #ERROR_SOCKET_NOT_IDLE}, or {@code #ERROR_INVALID_SOCKET} if the socket
+ * experienced an error (as in poll(2) returned POLLERR); if this happens, the data received
+ * from the socket may be invalid, and the socket can't be recovered.
+ * - If the socket has data in the send or receive buffer, then this call will fail with
+ * {@link #ERROR_SOCKET_NOT_IDLE} and can be retried after the data has been processed.
+ * An app could ensure this by using an application-layer protocol where it can receive
+ * acknowledgement that it will go into keepalive mode. It could then go into keepalive
+ * mode after having read the acknowledgement, draining the socket.
+ */
+ @Override
+ void startImpl(int intervalSec) {
+ try {
+ final FileDescriptor fd = mSocket.getFileDescriptor$();
+ mService.startTcpKeepalive(mNetwork, fd, intervalSec, mMessenger, new Binder());
+ } 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/ip/IIpClient.aidl b/core/java/android/net/ip/IIpClient.aidl
index 7769ec2..a4a80e1 100644
--- a/core/java/android/net/ip/IIpClient.aidl
+++ b/core/java/android/net/ip/IIpClient.aidl
@@ -17,6 +17,7 @@
import android.net.ProxyInfoParcelable;
import android.net.ProvisioningConfigurationParcelable;
+import android.net.TcpKeepalivePacketDataParcelable;
/** @hide */
oneway interface IIpClient {
@@ -29,4 +30,6 @@
void setTcpBufferSizes(in String tcpBufferSizes);
void setHttpProxy(in ProxyInfoParcelable proxyInfo);
void setMulticastFilter(boolean enabled);
+ void addKeepalivePacketFilter(int slot, in TcpKeepalivePacketDataParcelable pkt);
+ void removeKeepalivePacketFilter(int slot);
}
diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java
index de54a8aa..f63c0adb 100644
--- a/core/java/android/provider/CallLog.java
+++ b/core/java/android/provider/CallLog.java
@@ -772,19 +772,6 @@
* @param callBlockReason The reason why the call is blocked.
* @param callScreeningAppName The call screening application name which block the call.
* @param callScreeningComponentName The call screening component name which block the call.
- * @param callIdPackageName The package name of the
- * {@link android.telecom.CallScreeningService} which provided
- * {@link CallIdentification}.
- * @param callIdAppName The app name of the {@link android.telecom.CallScreeningService}
- * which provided {@link CallIdentification}.
- * @param callIdName The caller name provided by the
- * {@link android.telecom.CallScreeningService}.
- * @param callIdDescription The caller description provided by the
- * {@link android.telecom.CallScreeningService}.
- * @param callIdDetails The caller details provided by the
- * {@link android.telecom.CallScreeningService}.
- * @param callIdCallType The caller type provided by the
- * {@link android.telecom.CallScreeningService}.
*
* @result The URI of the call log entry belonging to the user that made or received this
* call. This could be of the shadow provider. Do not return it to non-system apps,
@@ -803,37 +790,10 @@
number, userToBeInsertedTo, addForAllUsers));
}
final ContentResolver resolver = context.getContentResolver();
- int numberPresentation = PRESENTATION_ALLOWED;
- TelecomManager tm = null;
- try {
- tm = TelecomManager.from(context);
- } catch (UnsupportedOperationException e) {}
+ String accountAddress = getLogAccountAddress(context, accountHandle);
- String accountAddress = null;
- if (tm != null && accountHandle != null) {
- PhoneAccount account = tm.getPhoneAccount(accountHandle);
- if (account != null) {
- Uri address = account.getSubscriptionAddress();
- if (address != null) {
- accountAddress = address.getSchemeSpecificPart();
- }
- }
- }
-
- // Remap network specified number presentation types
- // PhoneConstants.PRESENTATION_xxx to calllog number presentation types
- // Calls.PRESENTATION_xxx, in order to insulate the persistent calllog
- // from any future radio changes.
- // If the number field is empty set the presentation type to Unknown.
- if (presentation == PhoneConstants.PRESENTATION_RESTRICTED) {
- numberPresentation = PRESENTATION_RESTRICTED;
- } else if (presentation == PhoneConstants.PRESENTATION_PAYPHONE) {
- numberPresentation = PRESENTATION_PAYPHONE;
- } else if (TextUtils.isEmpty(number)
- || presentation == PhoneConstants.PRESENTATION_UNKNOWN) {
- numberPresentation = PRESENTATION_UNKNOWN;
- }
+ int numberPresentation = getLogNumberPresentation(number, presentation);
if (numberPresentation != PRESENTATION_ALLOWED) {
number = "";
if (ci != null) {
@@ -1138,8 +1098,7 @@
if (TextUtils.isEmpty(countryIso)) {
return;
}
- final String normalizedNumber = PhoneNumberUtils.formatNumberToE164(number,
- getCurrentCountryIso(context));
+ final String normalizedNumber = PhoneNumberUtils.formatNumberToE164(number, countryIso);
if (TextUtils.isEmpty(normalizedNumber)) {
return;
}
@@ -1148,6 +1107,54 @@
resolver.update(Data.CONTENT_URI, values, Data._ID + "=?", new String[] {dataId});
}
+ /**
+ * Remap network specified number presentation types
+ * PhoneConstants.PRESENTATION_xxx to calllog number presentation types
+ * Calls.PRESENTATION_xxx, in order to insulate the persistent calllog
+ * from any future radio changes.
+ * If the number field is empty set the presentation type to Unknown.
+ */
+ private static int getLogNumberPresentation(String number, int presentation) {
+ if (presentation == PhoneConstants.PRESENTATION_RESTRICTED) {
+ return presentation;
+ }
+
+ if (presentation == PhoneConstants.PRESENTATION_PAYPHONE) {
+ return presentation;
+ }
+
+ if (TextUtils.isEmpty(number)
+ || presentation == PhoneConstants.PRESENTATION_UNKNOWN) {
+ return PRESENTATION_UNKNOWN;
+ }
+
+ return PRESENTATION_ALLOWED;
+ }
+
+ private static String getLogAccountAddress(Context context,
+ PhoneAccountHandle accountHandle) {
+ TelecomManager tm = null;
+ try {
+ tm = TelecomManager.from(context);
+ } catch (UnsupportedOperationException e) {
+ if (VERBOSE_LOG) {
+ Log.v(LOG_TAG, "No TelecomManager found to get account address.");
+ }
+ }
+
+ String accountAddress = null;
+ if (tm != null && accountHandle != null) {
+ PhoneAccount account = tm.getPhoneAccount(accountHandle);
+ if (account != null) {
+ Uri address = account.getSubscriptionAddress();
+ if (address != null) {
+ accountAddress = address.getSchemeSpecificPart();
+ }
+ }
+ }
+ return accountAddress;
+ }
+
private static String getCurrentCountryIso(Context context) {
String countryIso = null;
final CountryDetector detector = (CountryDetector) context.getSystemService(
diff --git a/core/jni/OWNERS b/core/jni/OWNERS
index 86342c4..774c224 100644
--- a/core/jni/OWNERS
+++ b/core/jni/OWNERS
@@ -3,7 +3,7 @@
per-file *Camera*,*camera* = shuzhenwang@google.com, yinchiayeh@google.com, zhijunhe@google.com
# Connectivity
-per-file android_net_* = ek@google.com, lorenzo@google.com, satk@google.com
+per-file android_net_* = codewiz@google.com, jchalard@google.com, lorenzo@google.com, reminv@google.com, satk@google.com
# Zygote
per-file com_android_inernal_os_Zygote.*,fd_utils.* = chriswailes@google.com, ngeoffray@google.com, sehr@google.com, narayan@google.com, maco@google.com
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 7eddcfe..cfb2dd1 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -29,6 +29,7 @@
#include <net/if.h>
#include <linux/filter.h>
#include <linux/if_arp.h>
+#include <linux/tcp.h>
#include <netinet/ether.h>
#include <netinet/icmp6.h>
#include <netinet/ip.h>
@@ -226,6 +227,34 @@
}
}
+static void android_net_utils_attachDropAllBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd)
+{
+ struct sock_filter filter_code[] = {
+ // Reject all.
+ BPF_STMT(BPF_RET | BPF_K, 0)
+ };
+ struct sock_fprog filter = {
+ sizeof(filter_code) / sizeof(filter_code[0]),
+ filter_code,
+ };
+
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
+ if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(SO_ATTACH_FILTER): %s", strerror(errno));
+ }
+}
+
+static void android_net_utils_detachBPFFilter(JNIEnv *env, jobject clazz, jobject javaFd)
+{
+ int dummy = 0;
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
+ if (setsockopt(fd, SOL_SOCKET, SO_DETACH_FILTER, &dummy, sizeof(dummy)) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(SO_DETACH_FILTER): %s", strerror(errno));
+ }
+
+}
static void android_net_utils_setupRaSocket(JNIEnv *env, jobject clazz, jobject javaFd,
jint ifIndex)
{
@@ -458,6 +487,41 @@
return answer;
}
+static jobject android_net_utils_getTcpRepairWindow(JNIEnv *env, jobject thiz, jobject javaFd) {
+ if (javaFd == NULL) {
+ jniThrowNullPointerException(env, NULL);
+ return NULL;
+ }
+
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
+ struct tcp_repair_window trw = {};
+ socklen_t size = sizeof(trw);
+
+ // Obtain the parameters of the TCP repair window.
+ int rc = getsockopt(fd, IPPROTO_TCP, TCP_REPAIR_WINDOW, &trw, &size);
+ if (rc == -1) {
+ throwErrnoException(env, "getsockopt : TCP_REPAIR_WINDOW", errno);
+ return NULL;
+ }
+
+ struct tcp_info tcpinfo = {};
+ socklen_t tcpinfo_size = sizeof(tcp_info);
+
+ // Obtain the window scale from the tcp info structure. This contains a scale factor that
+ // should be applied to the window size.
+ rc = getsockopt(fd, IPPROTO_TCP, TCP_INFO, &tcpinfo, &tcpinfo_size);
+ if (rc == -1) {
+ throwErrnoException(env, "getsockopt : TCP_INFO", errno);
+ return NULL;
+ }
+
+ jclass class_TcpRepairWindow = env->FindClass("android/net/TcpRepairWindow");
+ jmethodID ctor = env->GetMethodID(class_TcpRepairWindow, "<init>", "(IIIIII)V");
+
+ return env->NewObject(class_TcpRepairWindow, ctor, trw.snd_wl1, trw.snd_wnd, trw.max_window,
+ trw.rcv_wnd, trw.rcv_wup, tcpinfo.tcpi_rcv_wscale);
+}
+
// ----------------------------------------------------------------------------
/*
@@ -475,6 +539,9 @@
{ "attachDhcpFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDhcpFilter },
{ "attachRaFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachRaFilter },
{ "attachControlPacketFilter", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_attachControlPacketFilter },
+ { "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter },
+ { "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter },
+ { "getTcpRepairWindow", "(Ljava/io/FileDescriptor;)Landroid/net/TcpRepairWindow;", (void*) android_net_utils_getTcpRepairWindow },
{ "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_setupRaSocket },
{ "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend },
{ "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
diff --git a/core/proto/android/bluetooth/enums.proto b/core/proto/android/bluetooth/enums.proto
index a88a06c..5b5c9c2 100644
--- a/core/proto/android/bluetooth/enums.proto
+++ b/core/proto/android/bluetooth/enums.proto
@@ -110,3 +110,25 @@
UNBOND_REASON_REMOTE_AUTH_CANCELED = 8;
UNBOND_REASON_REMOVED = 9;
}
+
+enum SocketTypeEnum {
+ SOCKET_TYPE_UNKNOWN = 0;
+ SOCKET_TYPE_RFCOMM = 1;
+ SOCKET_TYPE_SCO = 2;
+ SOCKET_TYPE_L2CAP_BREDR = 3;
+ SOCKET_TYPE_L2CAP_LE = 4;
+}
+
+enum SocketConnectionstateEnum {
+ SOCKET_CONNECTION_STATE_UNKNOWN = 0;
+ // Socket acts as a server waiting for connection
+ SOCKET_CONNECTION_STATE_LISTENING = 1;
+ // Socket acts as a client trying to connect
+ SOCKET_CONNECTION_STATE_CONNECTING = 2;
+ // Socket is connected
+ SOCKET_CONNECTION_STATE_CONNECTED = 3;
+ // Socket tries to disconnect from remote
+ SOCKET_CONNECTION_STATE_DISCONNECTING = 4;
+ // This socket is closed
+ SOCKET_CONNECTION_STATE_DISCONNECTED = 5;
+}
diff --git a/packages/NetworkStack/OWNERS b/packages/NetworkStack/OWNERS
new file mode 100644
index 0000000..a395465
--- /dev/null
+++ b/packages/NetworkStack/OWNERS
@@ -0,0 +1,5 @@
+codewiz@google.com
+jchalard@google.com
+lorenzo@google.com
+reminv@google.com
+satk@google.com
diff --git a/packages/NetworkStack/src/android/net/apf/ApfFilter.java b/packages/NetworkStack/src/android/net/apf/ApfFilter.java
index 4fa7d64..923f162 100644
--- a/packages/NetworkStack/src/android/net/apf/ApfFilter.java
+++ b/packages/NetworkStack/src/android/net/apf/ApfFilter.java
@@ -23,6 +23,7 @@
import static android.system.OsConstants.ETH_P_IP;
import static android.system.OsConstants.ETH_P_IPV6;
import static android.system.OsConstants.IPPROTO_ICMPV6;
+import static android.system.OsConstants.IPPROTO_TCP;
import static android.system.OsConstants.IPPROTO_UDP;
import static android.system.OsConstants.SOCK_RAW;
@@ -38,6 +39,7 @@
import android.content.IntentFilter;
import android.net.LinkAddress;
import android.net.LinkProperties;
+import android.net.TcpKeepalivePacketDataParcelable;
import android.net.apf.ApfGenerator.IllegalInstructionException;
import android.net.apf.ApfGenerator.Register;
import android.net.ip.IpClient.IpClientCallbacksWrapper;
@@ -55,6 +57,7 @@
import android.text.format.DateUtils;
import android.util.Log;
import android.util.Pair;
+import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -149,7 +152,9 @@
DROPPED_IPV6_NON_ICMP_MULTICAST,
DROPPED_802_3_FRAME,
DROPPED_ETHERTYPE_BLACKLISTED,
- DROPPED_ARP_REPLY_SPA_NO_HOST;
+ DROPPED_ARP_REPLY_SPA_NO_HOST,
+ DROPPED_IPV4_KEEPALIVE_ACK,
+ DROPPED_IPV6_KEEPALIVE_ACK;
// Returns the negative byte offset from the end of the APF data segment for
// a given counter.
@@ -285,6 +290,7 @@
private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16;
private static final int IPV4_ANY_HOST_ADDRESS = 0;
private static final int IPV4_BROADCAST_ADDRESS = -1; // 255.255.255.255
+ private static final int IPV4_HEADER_LEN = 20; // Without options
// Traffic class and Flow label are not byte aligned. Luckily we
// don't care about either value so we'll consider bytes 1-3 of the
@@ -305,6 +311,8 @@
private static final int UDP_DESTINATION_PORT_OFFSET = ETH_HEADER_LEN + 2;
private static final int UDP_HEADER_LEN = 8;
+ private static final int TCP_HEADER_SIZE_OFFSET = 12;
+
private static final int DHCP_CLIENT_PORT = 68;
// NOTE: this must be added to the IPv4 header length in IPV4_HEADER_SIZE_MEMORY_SLOT
private static final int DHCP_CLIENT_MAC_OFFSET = ETH_HEADER_LEN + UDP_HEADER_LEN + 28;
@@ -788,7 +796,7 @@
boolean isExpired() {
// TODO: We may want to handle 0 lifetime RAs differently, if they are common. We'll
- // have to calculte the filter lifetime specially as a fraction of 0 is still 0.
+ // have to calculate the filter lifetime specially as a fraction of 0 is still 0.
return currentLifetime() <= 0;
}
@@ -847,11 +855,147 @@
}
}
+ // A class to hold keepalive ack information.
+ private abstract static class TcpKeepaliveAck {
+ // Note that the offset starts from IP header.
+ // These must be added ether header length when generating program.
+ static final int IP_HEADER_OFFSET = 0;
+
+ protected static class TcpKeepaliveAckData {
+ public final byte[] srcAddress;
+ public final int srcPort;
+ public final byte[] dstAddress;
+ public final int dstPort;
+ public final int seq;
+ public final int ack;
+ // Create the characteristics of the ack packet from the sent keepalive packet.
+ TcpKeepaliveAckData(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) {
+ srcAddress = sentKeepalivePacket.dstAddress;
+ srcPort = sentKeepalivePacket.dstPort;
+ dstAddress = sentKeepalivePacket.srcAddress;
+ dstPort = sentKeepalivePacket.srcPort;
+ seq = sentKeepalivePacket.ack;
+ ack = sentKeepalivePacket.seq + 1;
+ }
+ }
+
+ protected final TcpKeepaliveAckData mPacket;
+ protected final byte[] mSrcDstAddr;
+
+ TcpKeepaliveAck(final TcpKeepaliveAckData packet, final byte[] srcDstAddr) {
+ mPacket = packet;
+ mSrcDstAddr = srcDstAddr;
+ }
+
+ static byte[] concatArrays(final byte[]... arr) {
+ int size = 0;
+ for (byte[] a : arr) {
+ size += a.length;
+ }
+ final byte[] result = new byte[size];
+ int offset = 0;
+ for (byte[] a : arr) {
+ System.arraycopy(a, 0, result, offset, a.length);
+ offset += a.length;
+ }
+ return result;
+ }
+
+ public String toString() {
+ return String.format("%s(%d) -> %s(%d), seq=%d, ack=%d",
+ mPacket.srcAddress,
+ mPacket.srcPort,
+ mPacket.dstAddress,
+ mPacket.dstPort,
+ mPacket.seq,
+ mPacket.ack);
+ }
+
+ // Append a filter for this keepalive ack to {@code gen}.
+ // Jump to drop if it matches the keepalive ack.
+ // Jump to the next filter if packet doesn't match the keepalive ack.
+ abstract void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException;
+ }
+
+ private class TcpKeepaliveAckV4 extends TcpKeepaliveAck {
+ private static final int IPV4_SRC_ADDR_OFFSET = IP_HEADER_OFFSET + 12;
+ private static final int IPV4_TCP_SRC_PORT_OFFSET = 0;
+ private static final int IPV4_TCP_DST_PORT_OFFSET = 2;
+ private static final int IPV4_TCP_SEQ_OFFSET = 4;
+ private static final int IPV4_TCP_ACK_OFFSET = 8;
+
+ TcpKeepaliveAckV4(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) {
+ this(new TcpKeepaliveAckData(sentKeepalivePacket));
+ }
+ TcpKeepaliveAckV4(final TcpKeepaliveAckData packet) {
+ super(packet, concatArrays(packet.srcAddress, packet.dstAddress) /* srcDstAddr */);
+ }
+
+ @Override
+ void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException {
+ final String nextFilterLabel = "keepalive_ack" + getUniqueNumberLocked();
+ gen.addLoad8(Register.R0, IPV4_PROTOCOL_OFFSET);
+ gen.addJumpIfR0NotEquals(IPPROTO_TCP, nextFilterLabel);
+ gen.addLoadImmediate(Register.R0, ETH_HEADER_LEN + IPV4_SRC_ADDR_OFFSET);
+ gen.addJumpIfBytesNotEqual(Register.R0, mSrcDstAddr, nextFilterLabel);
+
+ // Pass the packet if it's not zero-sized :
+ // Load the IP header size into R1
+ gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+ // Load the TCP header size into R0 (it's indexed by R1)
+ gen.addLoad8Indexed(Register.R0, ETH_HEADER_LEN + TCP_HEADER_SIZE_OFFSET);
+ // Size offset is in the top nibble, but it must be multiplied by 4, and the two
+ // top bits of the low nibble are guaranteed to be zeroes. Right-shift R0 by 2.
+ gen.addRightShift(2);
+ // R0 += R1 -> R0 contains TCP + IP headers lenght
+ gen.addAddR1();
+ // Add the Ethernet header length to R0.
+ gen.addLoadImmediate(Register.R1, ETH_HEADER_LEN);
+ gen.addAddR1();
+ // Compare total length of headers to the size of the packet.
+ gen.addLoadFromMemory(Register.R1, gen.PACKET_SIZE_MEMORY_SLOT);
+ gen.addNeg(Register.R0);
+ gen.addAddR1();
+ gen.addJumpIfR0NotEquals(0, nextFilterLabel);
+
+ // Add IPv4 header length
+ gen.addLoadFromMemory(Register.R1, gen.IPV4_HEADER_SIZE_MEMORY_SLOT);
+ gen.addLoad16Indexed(Register.R0, ETH_HEADER_LEN + IPV4_TCP_SRC_PORT_OFFSET);
+ gen.addJumpIfR0NotEquals(mPacket.srcPort, nextFilterLabel);
+ gen.addLoad16Indexed(Register.R0, ETH_HEADER_LEN + IPV4_TCP_DST_PORT_OFFSET);
+ gen.addJumpIfR0NotEquals(mPacket.dstPort, nextFilterLabel);
+ gen.addLoad32Indexed(Register.R0, ETH_HEADER_LEN + IPV4_TCP_SEQ_OFFSET);
+ gen.addJumpIfR0NotEquals(mPacket.seq, nextFilterLabel);
+ gen.addLoad32Indexed(Register.R0, ETH_HEADER_LEN + IPV4_TCP_ACK_OFFSET);
+ gen.addJumpIfR0NotEquals(mPacket.ack, nextFilterLabel);
+
+ maybeSetupCounter(gen, Counter.DROPPED_IPV4_KEEPALIVE_ACK);
+ gen.addJump(mCountAndDropLabel);
+ gen.defineLabel(nextFilterLabel);
+ }
+ }
+
+ private class TcpKeepaliveAckV6 extends TcpKeepaliveAck {
+ TcpKeepaliveAckV6(final TcpKeepalivePacketDataParcelable sentKeepalivePacket) {
+ this(new TcpKeepaliveAckData(sentKeepalivePacket));
+ }
+ TcpKeepaliveAckV6(final TcpKeepaliveAckData packet) {
+ super(packet, concatArrays(packet.srcAddress, packet.dstAddress) /* srcDstAddr */);
+ }
+
+ @Override
+ void generateFilterLocked(ApfGenerator gen) throws IllegalInstructionException {
+ throw new UnsupportedOperationException("IPv6 Keepalive is not supported yet");
+ }
+ }
+
// Maximum number of RAs to filter for.
private static final int MAX_RAS = 10;
@GuardedBy("this")
- private ArrayList<Ra> mRas = new ArrayList<Ra>();
+ private ArrayList<Ra> mRas = new ArrayList<>();
+ @GuardedBy("this")
+ private SparseArray<TcpKeepaliveAck> mKeepaliveAcks = new SparseArray<>();
// There is always some marginal benefit to updating the installed APF program when an RA is
// seen because we can extend the program's lifetime slightly, but there is some cost to
@@ -980,6 +1124,8 @@
// drop
// if it's IPv4 broadcast:
// drop
+ // if keepalive ack
+ // drop
// pass
if (mMulticastFilter) {
@@ -1023,6 +1169,9 @@
gen.addJumpIfR0Equals(broadcastAddr, mCountAndDropLabel);
}
+ // If any keepalive filters,
+ generateKeepaliveFilter(gen);
+
// If L2 broadcast packet, drop.
// TODO: can we invert this condition to fall through to the common pass case below?
maybeSetupCounter(gen, Counter.PASSED_IPV4_UNICAST);
@@ -1030,6 +1179,8 @@
gen.addJumpIfBytesNotEqual(Register.R0, ETH_BROADCAST_MAC_ADDRESS, mCountAndPassLabel);
maybeSetupCounter(gen, Counter.DROPPED_IPV4_L2_BROADCAST);
gen.addJump(mCountAndDropLabel);
+ } else {
+ generateKeepaliveFilter(gen);
}
// Otherwise, pass
@@ -1037,6 +1188,13 @@
gen.addJump(mCountAndPassLabel);
}
+ private void generateKeepaliveFilter(ApfGenerator gen) throws IllegalInstructionException {
+ // Drop IPv4 Keepalive acks
+ for (int i = 0; i < mKeepaliveAcks.size(); ++i) {
+ final TcpKeepaliveAck ack = mKeepaliveAcks.valueAt(i);
+ if (ack instanceof TcpKeepaliveAckV4) ack.generateFilterLocked(gen);
+ }
+ }
/**
* Generate filter code to process IPv6 packets. Execution of this code ends in either the
@@ -1057,6 +1215,8 @@
// drop
// if it's ICMPv6 NA to ff02::1:
// drop
+ // if keepalive ack
+ // drop
gen.addLoad8(Register.R0, IPV6_NEXT_HEADER_OFFSET);
@@ -1112,6 +1272,12 @@
maybeSetupCounter(gen, Counter.DROPPED_IPV6_MULTICAST_NA);
gen.addJump(mCountAndDropLabel);
gen.defineLabel(skipUnsolicitedMulticastNALabel);
+
+ // Drop IPv6 Keepalive acks
+ for (int i = 0; i < mKeepaliveAcks.size(); ++i) {
+ final TcpKeepaliveAck ack = mKeepaliveAcks.valueAt(i);
+ if (ack instanceof TcpKeepaliveAckV6) ack.generateFilterLocked(gen);
+ }
}
/**
@@ -1489,6 +1655,36 @@
installNewProgramLocked();
}
+ /**
+ * Add keepalive ack packet filter.
+ * This will add a filter to drop acks to the keepalive packet passed as an argument.
+ *
+ * @param slot The index used to access the filter.
+ * @param sentKeepalivePacket The attributes of the sent keepalive packet.
+ */
+ public synchronized void addKeepalivePacketFilter(final int slot,
+ final TcpKeepalivePacketDataParcelable sentKeepalivePacket) {
+ log("Adding keepalive ack(" + slot + ")");
+ if (null != mKeepaliveAcks.get(slot)) {
+ throw new IllegalArgumentException("Keepalive slot " + slot + " is occupied");
+ }
+ final int ipVersion = sentKeepalivePacket.srcAddress.length == 4 ? 4 : 6;
+ mKeepaliveAcks.put(slot, (ipVersion == 4)
+ ? new TcpKeepaliveAckV4(sentKeepalivePacket)
+ : new TcpKeepaliveAckV6(sentKeepalivePacket));
+ installNewProgramLocked();
+ }
+
+ /**
+ * Remove keepalive packet filter.
+ *
+ * @param slot The index used to access the filter.
+ */
+ public synchronized void removeKeepalivePacketFilter(int slot) {
+ mKeepaliveAcks.remove(slot);
+ installNewProgramLocked();
+ }
+
static public long counterValue(byte[] data, Counter counter)
throws ArrayIndexOutOfBoundsException {
// Follow the same wrap-around addressing scheme of the interpreter.
@@ -1541,6 +1737,17 @@
}
pw.decreaseIndent();
+ pw.println("Keepalive filter:");
+ pw.increaseIndent();
+ for (int i = 0; i < mKeepaliveAcks.size(); ++i) {
+ final TcpKeepaliveAck keepaliveAck = mKeepaliveAcks.valueAt(i);
+ pw.print("Slot ");
+ pw.print(mKeepaliveAcks.keyAt(i));
+ pw.print(" : ");
+ pw.println(keepaliveAck);
+ }
+ pw.decreaseIndent();
+
if (DBG) {
pw.println("Last program:");
pw.increaseIndent();
diff --git a/packages/NetworkStack/src/android/net/apf/ApfGenerator.java b/packages/NetworkStack/src/android/net/apf/ApfGenerator.java
index 87a1b5e..809327a 100644
--- a/packages/NetworkStack/src/android/net/apf/ApfGenerator.java
+++ b/packages/NetworkStack/src/android/net/apf/ApfGenerator.java
@@ -476,7 +476,7 @@
/**
* Add an instruction to the end of the program to load 16-bits from the packet into
- * {@code register}. The offset of the loaded 16-bits from the begining of the packet is
+ * {@code register}. The offset of the loaded 16-bits from the beginning of the packet is
* the sum of {@code offset} and the value in register R1.
*/
public ApfGenerator addLoad16Indexed(Register register, int offset) {
@@ -488,7 +488,7 @@
/**
* Add an instruction to the end of the program to load 32-bits from the packet into
- * {@code register}. The offset of the loaded 32-bits from the begining of the packet is
+ * {@code register}. The offset of the loaded 32-bits from the beginning of the packet is
* the sum of {@code offset} and the value in register R1.
*/
public ApfGenerator addLoad32Indexed(Register register, int offset) {
diff --git a/packages/NetworkStack/src/android/net/ip/IpClient.java b/packages/NetworkStack/src/android/net/ip/IpClient.java
index 12fe8c5..9e59912 100644
--- a/packages/NetworkStack/src/android/net/ip/IpClient.java
+++ b/packages/NetworkStack/src/android/net/ip/IpClient.java
@@ -23,6 +23,7 @@
import static com.android.server.util.PermissionUtil.checkNetworkStackCallingPermission;
+import android.annotation.NonNull;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.DhcpResults;
@@ -34,6 +35,7 @@
import android.net.ProxyInfo;
import android.net.ProxyInfoParcelable;
import android.net.RouteInfo;
+import android.net.TcpKeepalivePacketDataParcelable;
import android.net.apf.ApfCapabilities;
import android.net.apf.ApfFilter;
import android.net.dhcp.DhcpClient;
@@ -292,6 +294,8 @@
private static final int EVENT_PROVISIONING_TIMEOUT = 10;
private static final int EVENT_DHCPACTION_TIMEOUT = 11;
private static final int EVENT_READ_PACKET_FILTER_COMPLETE = 12;
+ private static final int CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF = 13;
+ private static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF = 14;
// Internal commands to use instead of trying to call transitionTo() inside
// a given State's enter() method. Calling transitionTo() from enter/exit
@@ -522,6 +526,16 @@
checkNetworkStackCallingPermission();
IpClient.this.setMulticastFilter(enabled);
}
+ @Override
+ public void addKeepalivePacketFilter(int slot, TcpKeepalivePacketDataParcelable pkt) {
+ checkNetworkStackCallingPermission();
+ IpClient.this.addKeepalivePacketFilter(slot, pkt);
+ }
+ @Override
+ public void removeKeepalivePacketFilter(int slot) {
+ checkNetworkStackCallingPermission();
+ IpClient.this.removeKeepalivePacketFilter(slot);
+ }
}
public String getInterfaceName() {
@@ -644,6 +658,22 @@
}
/**
+ * Called by WifiStateMachine to add keepalive packet filter before setting up
+ * keepalive offload.
+ */
+ public void addKeepalivePacketFilter(int slot, @NonNull TcpKeepalivePacketDataParcelable pkt) {
+ sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF, slot, 0 /* Unused */, pkt);
+ }
+
+ /**
+ * Called by WifiStateMachine to remove keepalive packet filter after stopping keepalive
+ * offload.
+ */
+ public void removeKeepalivePacketFilter(int slot) {
+ sendMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF, slot, 0 /* Unused */);
+ }
+
+ /**
* Dump logs of this IpClient.
*/
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
@@ -1512,6 +1542,23 @@
break;
}
+ case CMD_ADD_KEEPALIVE_PACKET_FILTER_TO_APF: {
+ final int slot = msg.arg1;
+ if (mApfFilter != null) {
+ mApfFilter.addKeepalivePacketFilter(slot,
+ (TcpKeepalivePacketDataParcelable) msg.obj);
+ }
+ break;
+ }
+
+ case CMD_REMOVE_KEEPALIVE_PACKET_FILTER_FROM_APF: {
+ final int slot = msg.arg1;
+ if (mApfFilter != null) {
+ mApfFilter.removeKeepalivePacketFilter(slot);
+ }
+ break;
+ }
+
case EVENT_DHCPACTION_TIMEOUT:
stopDhcpAction();
break;
diff --git a/packages/NetworkStack/tests/Android.bp b/packages/NetworkStack/tests/Android.bp
index 45fa2dc..4a09b3e 100644
--- a/packages/NetworkStack/tests/Android.bp
+++ b/packages/NetworkStack/tests/Android.bp
@@ -49,6 +49,7 @@
"libhidlbase",
"libhidltransport",
"libhwbinder",
+ "libjsoncpp",
"liblog",
"liblzma",
"libnativehelper",
diff --git a/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
index f76e412..a4a1000 100644
--- a/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
+++ b/packages/NetworkStack/tests/src/android/net/apf/ApfTest.java
@@ -39,13 +39,14 @@
import android.content.Context;
import android.net.LinkAddress;
import android.net.LinkProperties;
+import android.net.SocketKeepalive;
+import android.net.TcpKeepalivePacketData;
+import android.net.TcpKeepalivePacketData.TcpSocketInfo;
import android.net.apf.ApfFilter.ApfConfiguration;
import android.net.apf.ApfGenerator.IllegalInstructionException;
import android.net.apf.ApfGenerator.Register;
import android.net.ip.IIpClientCallbacks;
-import android.net.ip.IpClient;
import android.net.ip.IpClient.IpClientCallbacksWrapper;
-import android.net.ip.IpClientCallbacks;
import android.net.metrics.IpConnectivityLog;
import android.net.metrics.RaEvent;
import android.net.util.InterfaceParams;
@@ -1003,15 +1004,31 @@
private static final byte[] ETH_BROADCAST_MAC_ADDRESS =
{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff };
+ private static final int IPV4_HEADER_LEN = 20;
private static final int IPV4_VERSION_IHL_OFFSET = ETH_HEADER_LEN + 0;
+ private static final int IPV4_TOTAL_LENGTH_OFFSET = ETH_HEADER_LEN + 2;
private static final int IPV4_PROTOCOL_OFFSET = ETH_HEADER_LEN + 9;
+ private static final int IPV4_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 12;
private static final int IPV4_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 16;
+ private static final int IPV4_TCP_HEADER_LEN = 20;
+ private static final int IPV4_TCP_HEADER_OFFSET = ETH_HEADER_LEN + IPV4_HEADER_LEN;
+ private static final int IPV4_TCP_SRC_PORT_OFFSET = IPV4_TCP_HEADER_OFFSET + 0;
+ private static final int IPV4_TCP_DEST_PORT_OFFSET = IPV4_TCP_HEADER_OFFSET + 2;
+ private static final int IPV4_TCP_SEQ_NUM_OFFSET = IPV4_TCP_HEADER_OFFSET + 4;
+ private static final int IPV4_TCP_ACK_NUM_OFFSET = IPV4_TCP_HEADER_OFFSET + 8;
+ private static final int IPV4_TCP_HEADER_LENGTH_OFFSET = IPV4_TCP_HEADER_OFFSET + 12;
private static final byte[] IPV4_BROADCAST_ADDRESS =
{(byte) 255, (byte) 255, (byte) 255, (byte) 255};
private static final int IPV6_NEXT_HEADER_OFFSET = ETH_HEADER_LEN + 6;
private static final int IPV6_HEADER_LEN = 40;
+ private static final int IPV6_SRC_ADDR_OFFSET = ETH_HEADER_LEN + 8;
private static final int IPV6_DEST_ADDR_OFFSET = ETH_HEADER_LEN + 24;
+ private static final int IPV6_TCP_HEADER_OFFSET = ETH_HEADER_LEN + IPV6_HEADER_LEN;
+ private static final int IPV6_TCP_SRC_PORT_OFFSET = IPV6_TCP_HEADER_OFFSET + 0;
+ private static final int IPV6_TCP_DEST_PORT_OFFSET = IPV6_TCP_HEADER_OFFSET + 2;
+ private static final int IPV6_TCP_SEQ_NUM_OFFSET = IPV6_TCP_HEADER_OFFSET + 4;
+ private static final int IPV6_TCP_ACK_NUM_OFFSET = IPV6_TCP_HEADER_OFFSET + 8;
// The IPv6 all nodes address ff02::1
private static final byte[] IPV6_ALL_NODES_ADDRESS =
{ (byte) 0xff, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 };
@@ -1491,6 +1508,200 @@
return packet.array();
}
+ private static final byte[] IPV4_KEEPALIVE_SRC_ADDR = {10, 0, 0, 5};
+ private static final byte[] IPV4_KEEPALIVE_DST_ADDR = {10, 0, 0, 6};
+ private static final byte[] IPV4_ANOTHER_ADDR = {10, 0 , 0, 7};
+ private static final byte[] IPV6_KEEPALIVE_SRC_ADDR =
+ {(byte) 0x24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfa, (byte) 0xf1};
+ private static final byte[] IPV6_KEEPALIVE_DST_ADDR =
+ {(byte) 0x24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfa, (byte) 0xf2};
+ private static final byte[] IPV6_ANOTHER_ADDR =
+ {(byte) 0x24, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xfa, (byte) 0xf5};
+
+ @Test
+ public void testApfFilterKeepaliveAck() throws Exception {
+ final MockIpClientCallback cb = new MockIpClientCallback();
+ final ApfConfiguration config = getDefaultConfig();
+ config.multicastFilter = DROP_MULTICAST;
+ config.ieee802_3Filter = DROP_802_3_FRAMES;
+ final TestApfFilter apfFilter = new TestApfFilter(mContext, config, cb, mLog);
+ byte[] program;
+ final int srcPort = 12345;
+ final int dstPort = 54321;
+ final int seqNum = 2123456789;
+ final int ackNum = 1234567890;
+ final int anotherSrcPort = 23456;
+ final int anotherDstPort = 65432;
+ final int anotherSeqNum = 2123456780;
+ final int anotherAckNum = 1123456789;
+ final int slot1 = 1;
+ final int slot2 = 2;
+ final int window = 14480;
+ final int windowScale = 4;
+
+ // src: 10.0.0.5, port: 12345
+ // dst: 10.0.0.6, port: 54321
+ InetAddress srcAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_SRC_ADDR);
+ InetAddress dstAddr = InetAddress.getByAddress(IPV4_KEEPALIVE_DST_ADDR);
+
+ final TcpSocketInfo v4Tsi = new TcpSocketInfo(
+ srcAddr, srcPort, dstAddr, dstPort, seqNum, ackNum, window, windowScale);
+ final TcpKeepalivePacketData ipv4TcpKeepalivePacket =
+ TcpKeepalivePacketData.tcpKeepalivePacket(v4Tsi);
+
+ apfFilter.addKeepalivePacketFilter(slot1, ipv4TcpKeepalivePacket.toStableParcelable());
+ program = cb.getApfProgram();
+
+ // Verify IPv4 keepalive ack packet is dropped
+ // src: 10.0.0.6, port: 54321
+ // dst: 10.0.0.5, port: 12345
+ assertDrop(program,
+ ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, ackNum, seqNum + 1, 0 /* dataLength */));
+ // Verify IPv4 non-keepalive ack packet from the same source address is passed
+ assertPass(program,
+ ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, ackNum + 100, seqNum, 0 /* dataLength */));
+ assertPass(program,
+ ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, ackNum, seqNum + 1, 10 /* dataLength */));
+ // Verify IPv4 packet from another address is passed
+ assertPass(program,
+ ipv4Packet(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, anotherSrcPort,
+ anotherDstPort, anotherSeqNum, anotherAckNum));
+
+ // Remove IPv4 keepalive filter
+ apfFilter.removeKeepalivePacketFilter(slot1);
+
+ try {
+ // src: 2404:0:0:0:0:0:faf1, port: 12345
+ // dst: 2404:0:0:0:0:0:faf2, port: 54321
+ srcAddr = InetAddress.getByAddress(IPV6_KEEPALIVE_SRC_ADDR);
+ dstAddr = InetAddress.getByAddress(IPV6_KEEPALIVE_DST_ADDR);
+ final TcpSocketInfo v6Tsi = new TcpSocketInfo(
+ srcAddr, srcPort, dstAddr, dstPort, seqNum, ackNum, window, windowScale);
+ final TcpKeepalivePacketData ipv6TcpKeepalivePacket =
+ TcpKeepalivePacketData.tcpKeepalivePacket(v6Tsi);
+ apfFilter.addKeepalivePacketFilter(slot1, ipv6TcpKeepalivePacket.toStableParcelable());
+ program = cb.getApfProgram();
+
+ // Verify IPv6 keepalive ack packet is dropped
+ // src: 2404:0:0:0:0:0:faf2, port: 54321
+ // dst: 2404:0:0:0:0:0:faf1, port: 12345
+ assertDrop(program,
+ ipv6Packet(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, ackNum, seqNum + 1));
+ // Verify IPv6 non-keepalive ack packet from the same source address is passed
+ assertPass(program,
+ ipv6Packet(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, ackNum + 100, seqNum));
+ // Verify IPv6 packet from another address is passed
+ assertPass(program,
+ ipv6Packet(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, anotherSrcPort,
+ anotherDstPort, anotherSeqNum, anotherAckNum));
+
+ // Remove IPv6 keepalive filter
+ apfFilter.removeKeepalivePacketFilter(slot1);
+
+ // Verify multiple filters
+ apfFilter.addKeepalivePacketFilter(slot1, ipv4TcpKeepalivePacket.toStableParcelable());
+ apfFilter.addKeepalivePacketFilter(slot2, ipv6TcpKeepalivePacket.toStableParcelable());
+ program = cb.getApfProgram();
+
+ // Verify IPv4 keepalive ack packet is dropped
+ // src: 10.0.0.6, port: 54321
+ // dst: 10.0.0.5, port: 12345
+ assertDrop(program,
+ ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, ackNum, seqNum + 1));
+ // Verify IPv4 non-keepalive ack packet from the same source address is passed
+ assertPass(program,
+ ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, ackNum + 100, seqNum));
+ // Verify IPv4 packet from another address is passed
+ assertPass(program,
+ ipv4Packet(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, anotherSrcPort,
+ anotherDstPort, anotherSeqNum, anotherAckNum));
+
+ // Verify IPv6 keepalive ack packet is dropped
+ // src: 2404:0:0:0:0:0:faf2, port: 54321
+ // dst: 2404:0:0:0:0:0:faf1, port: 12345
+ assertDrop(program,
+ ipv6Packet(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, ackNum, seqNum + 1));
+ // Verify IPv6 non-keepalive ack packet from the same source address is passed
+ assertPass(program,
+ ipv6Packet(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, ackNum + 100, seqNum));
+ // Verify IPv6 packet from another address is passed
+ assertPass(program,
+ ipv6Packet(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, anotherSrcPort,
+ anotherDstPort, anotherSeqNum, anotherAckNum));
+
+ // Remove keepalive filters
+ apfFilter.removeKeepalivePacketFilter(slot1);
+ apfFilter.removeKeepalivePacketFilter(slot2);
+ } catch (SocketKeepalive.InvalidPacketException e) {
+ // TODO: support V6 packets
+ }
+
+ program = cb.getApfProgram();
+
+ // Verify IPv4, IPv6 packets are passed
+ assertPass(program,
+ ipv4Packet(IPV4_KEEPALIVE_DST_ADDR, IPV4_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, ackNum, seqNum + 1));
+ assertPass(program,
+ ipv6Packet(IPV6_KEEPALIVE_DST_ADDR, IPV6_KEEPALIVE_SRC_ADDR,
+ dstPort, srcPort, ackNum, seqNum + 1));
+ assertPass(program,
+ ipv4Packet(IPV4_ANOTHER_ADDR, IPV4_KEEPALIVE_SRC_ADDR, srcPort,
+ dstPort, anotherSeqNum, anotherAckNum));
+ assertPass(program,
+ ipv6Packet(IPV6_ANOTHER_ADDR, IPV6_KEEPALIVE_SRC_ADDR, srcPort,
+ dstPort, anotherSeqNum, anotherAckNum));
+
+ apfFilter.shutdown();
+ }
+
+ private static byte[] ipv4Packet(byte[] sip, byte[] tip, int sport,
+ int dport, int seq, int ack) {
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IP);
+ packet.put(IPV4_VERSION_IHL_OFFSET, (byte) 0x45);
+ put(packet, IPV4_SRC_ADDR_OFFSET, sip);
+ put(packet, IPV4_DEST_ADDR_OFFSET, tip);
+ packet.putShort(IPV4_TCP_SRC_PORT_OFFSET, (short) sport);
+ packet.putShort(IPV4_TCP_DEST_PORT_OFFSET, (short) dport);
+ packet.putInt(IPV4_TCP_SEQ_NUM_OFFSET, seq);
+ packet.putInt(IPV4_TCP_ACK_NUM_OFFSET, ack);
+ return packet.array();
+ }
+
+ private static byte[] ipv4Packet(byte[] sip, byte[] tip, int sport,
+ int dport, int seq, int ack, int dataLength) {
+ final int totalLength = dataLength + IPV4_HEADER_LEN + IPV4_TCP_HEADER_LEN;
+
+ ByteBuffer packet = ByteBuffer.wrap(ipv4Packet(sip, tip, sport, dport, seq, ack));
+ packet.putShort(IPV4_TOTAL_LENGTH_OFFSET, (short) totalLength);
+ // TCP header length 5, reserved 3 bits, NS=0
+ packet.put(IPV4_TCP_HEADER_LENGTH_OFFSET, (byte) 0x50);
+ return packet.array();
+ }
+
+ private static byte[] ipv6Packet(byte[] sip, byte[] tip, int sport,
+ int dport, int seq, int ack) {
+ ByteBuffer packet = ByteBuffer.wrap(new byte[100]);
+ packet.putShort(ETH_ETHERTYPE_OFFSET, (short) ETH_P_IPV6);
+ put(packet, IPV6_SRC_ADDR_OFFSET, sip);
+ put(packet, IPV6_DEST_ADDR_OFFSET, tip);
+ packet.putShort(IPV6_TCP_SRC_PORT_OFFSET, (short) sport);
+ packet.putShort(IPV6_TCP_DEST_PORT_OFFSET, (short) dport);
+ packet.putInt(IPV6_TCP_SEQ_NUM_OFFSET, seq);
+ packet.putInt(IPV6_TCP_ACK_NUM_OFFSET, ack);
+ return packet.array();
+ }
+
// Verify that the last program pushed to the IpClient.Callback properly filters the
// given packet for the given lifetime.
private void verifyRaLifetime(byte[] program, ByteBuffer packet, int lifetime) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index be46d2c..be7403a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -99,7 +99,6 @@
Log.d(TAG, "Connecting to HVAC service");
Dependency.get(HvacController.class).connectToCarService();
}
- mCarFacetButtonController = Dependency.get(CarFacetButtonController.class);
mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
mDeviceIsProvisioned = mDeviceProvisionedController.isDeviceProvisioned();
if (!mDeviceIsProvisioned) {
@@ -117,7 +116,7 @@
/**
* Remove all content from navbars and rebuild them. Used to allow for different nav bars
- * before and after the device is provisioned
+ * before and after the device is provisioned. Also for change of density and font size.
*/
private void restartNavBars() {
mCarFacetButtonController.removeAll();
@@ -216,6 +215,7 @@
protected void makeStatusBarView() {
super.makeStatusBarView();
+ mCarFacetButtonController = Dependency.get(CarFacetButtonController.class);
mNotificationPanelBackground = getDefaultWallpaper();
mScrimController.setScrimBehindDrawable(mNotificationPanelBackground);
@@ -513,6 +513,7 @@
@Override
public void onDensityOrFontScaleChanged() {
super.onDensityOrFontScaleChanged();
+ restartNavBars();
// Need to update the background on density changed in case the change was due to night
// mode.
mNotificationPanelBackground = getDefaultWallpaper();
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index ff44cbe..9faf00c 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -6348,6 +6348,14 @@
}
@Override
+ public void startTcpKeepalive(Network network, FileDescriptor fd, int intervalSeconds,
+ Messenger messenger, IBinder binder) {
+ enforceKeepalivePermission();
+ mKeepaliveTracker.startTcpKeepalive(
+ getNetworkAgentInfoForNetwork(network), fd, intervalSeconds, messenger, binder);
+ }
+
+ @Override
public void stopKeepalive(Network network, int slot) {
mHandler.sendMessage(mHandler.obtainMessage(
NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE, slot, SocketKeepalive.SUCCESS, network));
diff --git a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
index 07e28f9..6cff57d 100644
--- a/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
+++ b/services/core/java/com/android/server/connectivity/KeepaliveTracker.java
@@ -17,6 +17,8 @@
package com.android.server.connectivity;
import static android.net.NattSocketKeepalive.NATT_PORT;
+import static android.net.NetworkAgent.CMD_ADD_KEEPALIVE_PACKET_FILTER;
+import static android.net.NetworkAgent.CMD_REMOVE_KEEPALIVE_PACKET_FILTER;
import static android.net.NetworkAgent.CMD_START_SOCKET_KEEPALIVE;
import static android.net.NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE;
import static android.net.NetworkAgent.EVENT_SOCKET_KEEPALIVE;
@@ -37,6 +39,9 @@
import android.net.NetworkAgent;
import android.net.NetworkUtils;
import android.net.SocketKeepalive.InvalidPacketException;
+import android.net.SocketKeepalive.InvalidSocketException;
+import android.net.TcpKeepalivePacketData;
+import android.net.TcpKeepalivePacketData.TcpSocketInfo;
import android.net.util.IpUtils;
import android.os.Binder;
import android.os.Handler;
@@ -65,7 +70,7 @@
*
* Provides methods to stop and start keepalive requests, and keeps track of keepalives across all
* networks. This class is tightly coupled to ConnectivityService. It is not thread-safe and its
- * methods must be called only from the ConnectivityService handler thread.
+ * handle* methods must be called only from the ConnectivityService handler thread.
*/
public class KeepaliveTracker {
@@ -78,9 +83,12 @@
private final HashMap <NetworkAgentInfo, HashMap<Integer, KeepaliveInfo>> mKeepalives =
new HashMap<> ();
private final Handler mConnectivityServiceHandler;
+ @NonNull
+ private final TcpKeepaliveController mTcpController;
public KeepaliveTracker(Handler handler) {
mConnectivityServiceHandler = handler;
+ mTcpController = new TcpKeepaliveController(handler);
}
/**
@@ -96,20 +104,33 @@
private final int mUid;
private final int mPid;
private final NetworkAgentInfo mNai;
+ private final int mType;
+ private final FileDescriptor mFd;
- /** Keepalive slot. A small integer that identifies this keepalive among the ones handled
- * by this network. */
+ public static final int TYPE_NATT = 1;
+ public static final int TYPE_TCP = 2;
+
+ // Keepalive slot. A small integer that identifies this keepalive among the ones handled
+ // by this network.
private int mSlot = NO_KEEPALIVE;
// Packet data.
private final KeepalivePacketData mPacket;
private final int mInterval;
- // Whether the keepalive is started or not.
- public boolean isStarted;
+ // Whether the keepalive is started or not. The initial state is NOT_STARTED.
+ private static final int NOT_STARTED = 1;
+ private static final int STARTING = 2;
+ private static final int STARTED = 3;
+ private int mStartedState = NOT_STARTED;
- public KeepaliveInfo(Messenger messenger, IBinder binder, NetworkAgentInfo nai,
- KeepalivePacketData packet, int interval) {
+ KeepaliveInfo(@NonNull Messenger messenger,
+ @NonNull IBinder binder,
+ @NonNull NetworkAgentInfo nai,
+ @NonNull KeepalivePacketData packet,
+ int interval,
+ int type,
+ @NonNull FileDescriptor fd) {
mMessenger = messenger;
mBinder = binder;
mPid = Binder.getCallingPid();
@@ -118,6 +139,8 @@
mNai = nai;
mPacket = packet;
mInterval = interval;
+ mType = type;
+ mFd = fd;
try {
mBinder.linkToDeath(this, 0);
@@ -130,32 +153,40 @@
return mNai;
}
+ private String startedStateString(final int state) {
+ switch (state) {
+ case NOT_STARTED : return "NOT_STARTED";
+ case STARTING : return "STARTING";
+ case STARTED : return "STARTED";
+ }
+ throw new IllegalArgumentException("Unknown state");
+ }
+
public String toString() {
- return new StringBuffer("KeepaliveInfo [")
- .append(" network=").append(mNai.network)
- .append(" isStarted=").append(isStarted)
- .append(" ")
- .append(IpUtils.addressAndPortToString(mPacket.srcAddress, mPacket.srcPort))
- .append("->")
- .append(IpUtils.addressAndPortToString(mPacket.dstAddress, mPacket.dstPort))
- .append(" interval=" + mInterval)
- .append(" packetData=" + HexDump.toHexString(mPacket.getPacket()))
- .append(" uid=").append(mUid).append(" pid=").append(mPid)
- .append(" ]")
- .toString();
+ return "KeepaliveInfo ["
+ + " network=" + mNai.network
+ + " startedState=" + startedStateString(mStartedState)
+ + " "
+ + IpUtils.addressAndPortToString(mPacket.srcAddress, mPacket.srcPort)
+ + "->"
+ + IpUtils.addressAndPortToString(mPacket.dstAddress, mPacket.dstPort)
+ + " interval=" + mInterval
+ + " uid=" + mUid + " pid=" + mPid
+ + " packetData=" + HexDump.toHexString(mPacket.getPacket())
+ + " ]";
}
/** Sends a message back to the application via its SocketKeepalive.Callback. */
void notifyMessenger(int slot, int err) {
+ if (DBG) {
+ Log.d(TAG, "notify keepalive " + mSlot + " on " + mNai.network + " for " + err);
+ }
KeepaliveTracker.this.notifyMessenger(mMessenger, slot, err);
}
/** Called when the application process is killed. */
public void binderDied() {
- // Not called from ConnectivityService handler thread, so send it a message.
- mConnectivityServiceHandler.obtainMessage(
- NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE,
- mSlot, BINDER_DIED, mNai.network).sendToTarget();
+ stop(BINDER_DIED);
}
void unlinkDeathRecipient() {
@@ -202,7 +233,26 @@
int error = isValid();
if (error == SUCCESS) {
Log.d(TAG, "Starting keepalive " + mSlot + " on " + mNai.name());
- mNai.asyncChannel.sendMessage(CMD_START_SOCKET_KEEPALIVE, slot, mInterval, mPacket);
+ switch (mType) {
+ case TYPE_NATT:
+ mNai.asyncChannel
+ .sendMessage(CMD_START_SOCKET_KEEPALIVE, slot, mInterval, mPacket);
+ break;
+ case TYPE_TCP:
+ mTcpController.startSocketMonitor(mFd, this, mSlot);
+ mNai.asyncChannel
+ .sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER, slot, 0 /* Unused */,
+ mPacket);
+ // TODO: check result from apf and notify of failure as needed.
+ mNai.asyncChannel
+ .sendMessage(CMD_START_SOCKET_KEEPALIVE, slot, mInterval, mPacket);
+ break;
+ default:
+ Log.wtf(TAG, "Starting keepalive with unknown type: " + mType);
+ handleStopKeepalive(mNai, mSlot, error);
+ return;
+ }
+ mStartedState = STARTING;
} else {
handleStopKeepalive(mNai, mSlot, error);
return;
@@ -216,15 +266,27 @@
Log.e(TAG, "Cannot stop unowned keepalive " + mSlot + " on " + mNai.network);
}
}
- if (isStarted) {
+ if (NOT_STARTED != mStartedState) {
Log.d(TAG, "Stopping keepalive " + mSlot + " on " + mNai.name());
- mNai.asyncChannel.sendMessage(CMD_STOP_SOCKET_KEEPALIVE, mSlot);
+ if (mType == TYPE_NATT) {
+ mNai.asyncChannel.sendMessage(CMD_STOP_SOCKET_KEEPALIVE, mSlot);
+ } else if (mType == TYPE_TCP) {
+ mNai.asyncChannel.sendMessage(CMD_STOP_SOCKET_KEEPALIVE, mSlot);
+ mNai.asyncChannel.sendMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, mSlot);
+ mTcpController.stopSocketMonitor(mSlot);
+ } else {
+ Log.wtf(TAG, "Stopping keepalive with unknown type: " + mType);
+ }
}
// TODO: at the moment we unconditionally return failure here. In cases where the
// NetworkAgent is alive, should we ask it to reply, so it can return failure?
notifyMessenger(mSlot, reason);
unlinkDeathRecipient();
}
+
+ void onFileDescriptorInitiatedStop(final int socketKeepaliveReason) {
+ handleStopKeepalive(mNai, mSlot, socketKeepaliveReason);
+ }
}
void notifyMessenger(Messenger messenger, int slot, int err) {
@@ -328,20 +390,38 @@
return;
}
- if (reason == SUCCESS && !ki.isStarted) {
+ // This can be called in a number of situations :
+ // - startedState is STARTING.
+ // - reason is SUCCESS => go to STARTED.
+ // - reason isn't SUCCESS => it's an error starting. Go to NOT_STARTED and stop keepalive.
+ // - startedState is STARTED.
+ // - reason is SUCCESS => it's a success stopping. Go to NOT_STARTED and stop keepalive.
+ // - reason isn't SUCCESS => it's an error in exec. Go to NOT_STARTED and stop keepalive.
+ // The control is not supposed to ever come here if the state is NOT_STARTED. This is
+ // because in NOT_STARTED state, the code will switch to STARTING before sending messages
+ // to start, and the only way to NOT_STARTED is this function, through the edges outlined
+ // above : in all cases, keepalive gets stopped and can't restart without going into
+ // STARTING as messages are ordered. This also depends on the hardware processing the
+ // messages in order.
+ // TODO : clarify this code and get rid of mStartedState. Using a StateMachine is an
+ // option.
+ if (reason == SUCCESS && KeepaliveInfo.STARTING == ki.mStartedState) {
// Keepalive successfully started.
if (DBG) Log.d(TAG, "Started keepalive " + slot + " on " + nai.name());
- ki.isStarted = true;
+ ki.mStartedState = KeepaliveInfo.STARTED;
ki.notifyMessenger(slot, reason);
} else {
// Keepalive successfully stopped, or error.
- ki.isStarted = false;
+ ki.mStartedState = KeepaliveInfo.NOT_STARTED;
if (reason == SUCCESS) {
+ // The message indicated success stopping : don't call handleStopKeepalive.
if (DBG) Log.d(TAG, "Successfully stopped keepalive " + slot + " on " + nai.name());
} else {
+ // The message indicated some error trying to start or during the course of
+ // keepalive : do call handleStopKeepalive.
+ handleStopKeepalive(nai, slot, reason);
if (DBG) Log.d(TAG, "Keepalive " + slot + " on " + nai.name() + " error " + reason);
}
- handleStopKeepalive(nai, slot, reason);
}
}
@@ -379,10 +459,49 @@
notifyMessenger(messenger, NO_KEEPALIVE, e.error);
return;
}
- KeepaliveInfo ki = new KeepaliveInfo(messenger, binder, nai, packet, intervalSeconds);
- Log.d(TAG, "Created keepalive: " + ki.toString());
+ KeepaliveInfo ki = new KeepaliveInfo(messenger, binder, nai, packet, intervalSeconds,
+ KeepaliveInfo.TYPE_NATT, null);
mConnectivityServiceHandler.obtainMessage(
- CMD_START_SOCKET_KEEPALIVE, ki).sendToTarget();
+ NetworkAgent.CMD_START_SOCKET_KEEPALIVE, ki).sendToTarget();
+ }
+
+ /**
+ * Called by ConnectivityService to start TCP keepalive on a file descriptor.
+ *
+ * In order to offload keepalive for application correctly, sequence number, ack number and
+ * other fields are needed to form the keepalive packet. Thus, this function synchronously
+ * puts the socket into repair mode to get the necessary information. After the socket has been
+ * put into repair mode, the application cannot access the socket until reverted to normal.
+ *
+ * See {@link android.net.SocketKeepalive}.
+ **/
+ public void startTcpKeepalive(@Nullable NetworkAgentInfo nai,
+ @NonNull FileDescriptor fd,
+ int intervalSeconds,
+ @NonNull Messenger messenger,
+ @NonNull IBinder binder) {
+ if (nai == null) {
+ notifyMessenger(messenger, NO_KEEPALIVE, ERROR_INVALID_NETWORK);
+ return;
+ }
+
+ TcpKeepalivePacketData packet = null;
+ try {
+ TcpSocketInfo tsi = TcpKeepaliveController.switchToRepairMode(fd);
+ packet = TcpKeepalivePacketData.tcpKeepalivePacket(tsi);
+ } catch (InvalidPacketException | InvalidSocketException e) {
+ try {
+ TcpKeepaliveController.switchOutOfRepairMode(fd);
+ } catch (ErrnoException e1) {
+ Log.e(TAG, "Couldn't move fd out of repair mode after failure to start keepalive");
+ }
+ notifyMessenger(messenger, NO_KEEPALIVE, e.error);
+ return;
+ }
+ KeepaliveInfo ki = new KeepaliveInfo(messenger, binder, nai, packet, intervalSeconds,
+ KeepaliveInfo.TYPE_TCP, fd);
+ Log.d(TAG, "Created keepalive: " + ki.toString());
+ mConnectivityServiceHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, ki).sendToTarget();
}
/**
diff --git a/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
new file mode 100644
index 0000000..8a9ac23
--- /dev/null
+++ b/services/core/java/com/android/server/connectivity/TcpKeepaliveController.java
@@ -0,0 +1,316 @@
+/*
+ * 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.connectivity;
+
+import static android.net.SocketKeepalive.DATA_RECEIVED;
+import static android.net.SocketKeepalive.ERROR_INVALID_SOCKET;
+import static android.net.SocketKeepalive.ERROR_SOCKET_NOT_IDLE;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_ERROR;
+import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT;
+import static android.system.OsConstants.FIONREAD;
+import static android.system.OsConstants.IPPROTO_TCP;
+import static android.system.OsConstants.TIOCOUTQ;
+
+import android.annotation.NonNull;
+import android.net.NetworkUtils;
+import android.net.SocketKeepalive.InvalidSocketException;
+import android.net.TcpKeepalivePacketData.TcpSocketInfo;
+import android.net.TcpRepairWindow;
+import android.os.Handler;
+import android.os.MessageQueue;
+import android.os.Messenger;
+import android.system.ErrnoException;
+import android.system.Int32Ref;
+import android.system.Os;
+import android.util.Log;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.connectivity.KeepaliveTracker.KeepaliveInfo;
+
+import java.io.FileDescriptor;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.SocketException;
+
+/**
+ * Manage tcp socket which offloads tcp keepalive.
+ *
+ * The input socket will be changed to repair mode and the application
+ * will not have permission to read/write data. If the application wants
+ * to write data, it must stop tcp keepalive offload to leave repair mode
+ * first. If a remote packet arrives, repair mode will be turned off and
+ * offload will be stopped. The application will receive a callback to know
+ * it can start reading data.
+ *
+ * {start,stop}SocketMonitor are thread-safe, but care must be taken in the
+ * order in which they are called. Please note that while calling
+ * {@link #startSocketMonitor(FileDescriptor, Messenger, int)} multiple times
+ * with either the same slot or the same FileDescriptor without stopping it in
+ * between will result in an exception, calling {@link #stopSocketMonitor(int)}
+ * multiple times with the same int is explicitly a no-op.
+ * Please also note that switching the socket to repair mode is not synchronized
+ * with either of these operations and has to be done in an orderly fashion
+ * with stopSocketMonitor. Take care in calling these in the right order.
+ * @hide
+ */
+public class TcpKeepaliveController {
+ private static final String TAG = "TcpKeepaliveController";
+ private static final boolean DBG = false;
+
+ private final MessageQueue mFdHandlerQueue;
+
+ private static final int FD_EVENTS = EVENT_INPUT | EVENT_ERROR;
+
+ // Reference include/uapi/linux/tcp.h
+ private static final int TCP_REPAIR = 19;
+ private static final int TCP_REPAIR_QUEUE = 20;
+ private static final int TCP_QUEUE_SEQ = 21;
+ private static final int TCP_NO_QUEUE = 0;
+ private static final int TCP_RECV_QUEUE = 1;
+ private static final int TCP_SEND_QUEUE = 2;
+ private static final int TCP_REPAIR_OFF = 0;
+ private static final int TCP_REPAIR_ON = 1;
+ // Reference include/uapi/linux/sockios.h
+ private static final int SIOCINQ = FIONREAD;
+ private static final int SIOCOUTQ = TIOCOUTQ;
+
+ /**
+ * Keeps track of packet listeners.
+ * Key: slot number of keepalive offload.
+ * Value: {@link FileDescriptor} being listened to.
+ */
+ @GuardedBy("mListeners")
+ private final SparseArray<FileDescriptor> mListeners = new SparseArray<>();
+
+ public TcpKeepaliveController(final Handler connectivityServiceHandler) {
+ mFdHandlerQueue = connectivityServiceHandler.getLooper().getQueue();
+ }
+
+ /**
+ * Switch the tcp socket to repair mode and query tcp socket information.
+ *
+ * @param fd the fd of socket on which to use keepalive offload
+ * @return a {@link TcpKeepalivePacketData#TcpSocketInfo} object for current
+ * tcp/ip information.
+ */
+ // TODO : make this private. It's far too confusing that this gets called from outside
+ // at a time that nobody can understand.
+ public static TcpSocketInfo switchToRepairMode(FileDescriptor fd)
+ throws InvalidSocketException {
+ if (DBG) Log.i(TAG, "switchToRepairMode to start tcp keepalive : " + fd);
+ final SocketAddress srcSockAddr;
+ final SocketAddress dstSockAddr;
+ final InetAddress srcAddress;
+ final InetAddress dstAddress;
+ final int srcPort;
+ final int dstPort;
+ int seq;
+ final int ack;
+ final TcpRepairWindow trw;
+
+ // Query source address and port.
+ try {
+ srcSockAddr = Os.getsockname(fd);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Get sockname fail: ", e);
+ throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
+ }
+ if (srcSockAddr instanceof InetSocketAddress) {
+ srcAddress = getAddress((InetSocketAddress) srcSockAddr);
+ srcPort = getPort((InetSocketAddress) srcSockAddr);
+ } else {
+ Log.e(TAG, "Invalid or mismatched SocketAddress");
+ throw new InvalidSocketException(ERROR_INVALID_SOCKET);
+ }
+ // Query destination address and port.
+ try {
+ dstSockAddr = Os.getpeername(fd);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Get peername fail: ", e);
+ throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
+ }
+ if (dstSockAddr instanceof InetSocketAddress) {
+ dstAddress = getAddress((InetSocketAddress) dstSockAddr);
+ dstPort = getPort((InetSocketAddress) dstSockAddr);
+ } else {
+ Log.e(TAG, "Invalid or mismatched peer SocketAddress");
+ throw new InvalidSocketException(ERROR_INVALID_SOCKET);
+ }
+
+ // Query sequence and ack number
+ dropAllIncomingPackets(fd, true);
+ try {
+ // Enter tcp repair mode.
+ Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR, TCP_REPAIR_ON);
+ // Check if socket is idle.
+ if (!isSocketIdle(fd)) {
+ throw new InvalidSocketException(ERROR_SOCKET_NOT_IDLE);
+ }
+ // Query write sequence number from SEND_QUEUE.
+ Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_SEND_QUEUE);
+ seq = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
+ // Query read sequence number from RECV_QUEUE.
+ Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_RECV_QUEUE);
+ ack = Os.getsockoptInt(fd, IPPROTO_TCP, TCP_QUEUE_SEQ);
+ // Switch to NO_QUEUE to prevent illegal socket read/write in repair mode.
+ Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR_QUEUE, TCP_NO_QUEUE);
+ // Finally, check if socket is still idle. TODO : this check needs to move to
+ // after starting polling to prevent a race.
+ if (!isSocketIdle(fd)) {
+ throw new InvalidSocketException(ERROR_INVALID_SOCKET);
+ }
+
+ // Query tcp window size.
+ trw = NetworkUtils.getTcpRepairWindow(fd);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Exception reading TCP state from socket", e);
+ try {
+ Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR, TCP_REPAIR_OFF);
+ } catch (ErrnoException ex) {
+ Log.e(TAG, "Exception while turning off repair mode due to exception", ex);
+ }
+ throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
+ } finally {
+ dropAllIncomingPackets(fd, false);
+ }
+
+ // Keepalive sequence number is last sequence number - 1. If it couldn't be retrieved,
+ // then it must be set to -1, so decrement in all cases.
+ seq = seq - 1;
+
+ return new TcpSocketInfo(srcAddress, srcPort, dstAddress, dstPort, seq, ack, trw.rcvWnd,
+ trw.rcvWndScale);
+ }
+
+ /**
+ * Switch the tcp socket out of repair mode.
+ *
+ * @param fd the fd of socket to switch back to normal.
+ */
+ // TODO : make this private.
+ public static void switchOutOfRepairMode(@NonNull final FileDescriptor fd)
+ throws ErrnoException {
+ Os.setsockoptInt(fd, IPPROTO_TCP, TCP_REPAIR, TCP_REPAIR_OFF);
+ }
+
+ /**
+ * Start monitoring incoming packets.
+ *
+ * @param fd socket fd to monitor.
+ * @param messenger a callback to notify socket status.
+ * @param slot keepalive slot.
+ */
+ public void startSocketMonitor(@NonNull final FileDescriptor fd,
+ @NonNull final KeepaliveInfo ki, final int slot) {
+ synchronized (mListeners) {
+ if (null != mListeners.get(slot)) {
+ throw new IllegalArgumentException("This slot is already taken");
+ }
+ for (int i = 0; i < mListeners.size(); ++i) {
+ if (fd.equals(mListeners.valueAt(i))) {
+ throw new IllegalArgumentException("This fd is already registered");
+ }
+ }
+ mFdHandlerQueue.addOnFileDescriptorEventListener(fd, FD_EVENTS, (readyFd, events) -> {
+ // This can't be called twice because the queue guarantees that once the listener
+ // is unregistered it can't be called again, even for a message that arrived
+ // before it was unregistered.
+ final int reason;
+ if (0 != (events & EVENT_ERROR)) {
+ reason = ERROR_INVALID_SOCKET;
+ } else {
+ reason = DATA_RECEIVED;
+ }
+ ki.onFileDescriptorInitiatedStop(reason);
+ // The listener returns the new set of events to listen to. Because 0 means no
+ // event, the listener gets unregistered.
+ return 0;
+ });
+ mListeners.put(slot, fd);
+ }
+ }
+
+ /** Stop socket monitor */
+ // This slot may have been stopped automatically already because the socket received data,
+ // was closed on the other end or otherwise suffered some error. In this case, this function
+ // is a no-op.
+ public void stopSocketMonitor(final int slot) {
+ final FileDescriptor fd;
+ synchronized (mListeners) {
+ fd = mListeners.get(slot);
+ if (null == fd) return;
+ mListeners.remove(slot);
+ }
+ mFdHandlerQueue.removeOnFileDescriptorEventListener(fd);
+ try {
+ if (DBG) Log.d(TAG, "Moving socket out of repair mode for stop : " + fd);
+ switchOutOfRepairMode(fd);
+ } catch (ErrnoException e) {
+ Log.e(TAG, "Cannot switch socket out of repair mode", e);
+ // Well, there is not much to do here to recover
+ }
+ }
+
+ private static InetAddress getAddress(InetSocketAddress inetAddr) {
+ return inetAddr.getAddress();
+ }
+
+ private static int getPort(InetSocketAddress inetAddr) {
+ return inetAddr.getPort();
+ }
+
+ private static boolean isSocketIdle(FileDescriptor fd) throws ErrnoException {
+ return isReceiveQueueEmpty(fd) && isSendQueueEmpty(fd);
+ }
+
+ private static boolean isReceiveQueueEmpty(FileDescriptor fd)
+ throws ErrnoException {
+ Int32Ref result = new Int32Ref(-1);
+ Os.ioctlInt(fd, SIOCINQ, result);
+ if (result.value != 0) {
+ Log.e(TAG, "Read queue has data");
+ return false;
+ }
+ return true;
+ }
+
+ private static boolean isSendQueueEmpty(FileDescriptor fd)
+ throws ErrnoException {
+ Int32Ref result = new Int32Ref(-1);
+ Os.ioctlInt(fd, SIOCOUTQ, result);
+ if (result.value != 0) {
+ Log.e(TAG, "Write queue has data");
+ return false;
+ }
+ return true;
+ }
+
+ private static void dropAllIncomingPackets(FileDescriptor fd, boolean enable)
+ throws InvalidSocketException {
+ try {
+ if (enable) {
+ NetworkUtils.attachDropAllBPFFilter(fd);
+ } else {
+ NetworkUtils.detachBPFFilter(fd);
+ }
+ } catch (SocketException e) {
+ Log.e(TAG, "Socket Exception: ", e);
+ throw new InvalidSocketException(ERROR_INVALID_SOCKET, e);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
deleted file mode 100644
index 5f71b0b..0000000
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.timezonedetector;
-
-import com.android.internal.util.DumpUtils;
-import com.android.server.SystemService;
-import android.app.timezonedetector.ITimeZoneDetectorService;
-import android.content.Context;
-import android.util.Slog;
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-
-public final class TimeZoneDetectorService extends ITimeZoneDetectorService.Stub {
- private static final String TAG = "timezonedetector.TimeZoneDetectorService";
-
- public static class Lifecycle extends SystemService {
-
- public Lifecycle(Context context) {
- super(context);
- }
-
- @Override
- public void onStart() {
- TimeZoneDetectorService service = TimeZoneDetectorService.create(getContext());
- // Publish the binder service so it can be accessed from other (appropriately
- // permissioned) processes.
- publishBinderService(Context.TIME_ZONE_DETECTOR_SERVICE, service);
- }
- }
-
- private final Context mContext;
-
- private static TimeZoneDetectorService create(Context context) {
- return new TimeZoneDetectorService(context);
- }
-
- public TimeZoneDetectorService(Context context) {
- mContext = context;
- }
-
- @Override
- public void stubbedCall() {
- // Empty call for initial tests.
- Slog.d(TAG, "stubbedCall() called");
- // TODO: Remove when there are real methods.
- }
-
- @Override
- protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
- // TODO: Implement when there is state.
- }
-}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index d5e59c8..c30babd 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -128,6 +128,8 @@
private final WatchLogHandler mWatchLogHandler;
+ private IBinder.DeathRecipient mDeathRecipient;
+
public TvInputManagerService(Context context) {
super(context);
@@ -672,6 +674,7 @@
if (sessionToken == userState.mainSessionToken) {
setMainLocked(sessionToken, false, callingUid, userId);
}
+ sessionState.session.asBinder().unlinkToDeath(sessionState, 0);
sessionState.session.release();
}
} catch (RemoteException | SessionNotFoundException e) {
@@ -707,6 +710,7 @@
clientState.sessionTokens.remove(sessionToken);
if (clientState.isEmpty()) {
userState.clientStateMap.remove(sessionState.client.asBinder());
+ sessionState.client.asBinder().unlinkToDeath(clientState, 0);
}
}
@@ -1000,17 +1004,19 @@
synchronized (mLock) {
final UserState userState = getOrCreateUserStateLocked(resolvedUserId);
userState.callbackSet.add(callback);
- try {
- callback.asBinder().linkToDeath(new IBinder.DeathRecipient() {
- @Override
- public void binderDied() {
- synchronized (mLock) {
- if (userState.callbackSet != null) {
- userState.callbackSet.remove(callback);
- }
+ mDeathRecipient = new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ synchronized (mLock) {
+ if (userState.callbackSet != null) {
+ userState.callbackSet.remove(callback);
}
}
- }, 0);
+ }
+ };
+
+ try {
+ callback.asBinder().linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
Slog.e(TAG, "client process has already died", e);
}
@@ -1029,6 +1035,7 @@
synchronized (mLock) {
UserState userState = getOrCreateUserStateLocked(resolvedUserId);
userState.callbackSet.remove(callback);
+ callback.asBinder().unlinkToDeath(mDeathRecipient, 0);
}
} finally {
Binder.restoreCallingIdentity(identity);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 75cd82e..2338fff 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -238,8 +238,6 @@
"com.android.internal.car.CarServiceHelperService";
private static final String TIME_DETECTOR_SERVICE_CLASS =
"com.android.server.timedetector.TimeDetectorService$Lifecycle";
- private static final String TIME_ZONE_DETECTOR_SERVICE_CLASS =
- "com.android.server.timezonedetector.TimeZoneDetectorService$Lifecycle";
private static final String PERSISTENT_DATA_BLOCK_PROP = "ro.frp.pst";
@@ -1316,14 +1314,6 @@
reportWtf("starting StartTimeDetectorService service", e);
}
traceEnd();
-
- traceBeginAndSlog("StartTimeZoneDetectorService");
- try {
- mSystemServiceManager.startService(TIME_ZONE_DETECTOR_SERVICE_CLASS);
- } catch (Throwable e) {
- reportWtf("starting StartTimeZoneDetectorService service", e);
- }
- traceEnd();
}
if (!isWatch) {
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
deleted file mode 100644
index 19d31cf..0000000
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.timezonedetector;
-
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Unit tests for the {@link TimeZoneDetectorService}.
- */
-@RunWith(AndroidJUnit4.class)
-public class TimeZoneDetectorServiceTest {
-
- private TimeZoneDetectorService mTimeZoneDetectorService;
-
- @Before
- public void setUp() {
- final Context context = InstrumentationRegistry.getContext();
- mTimeZoneDetectorService = new TimeZoneDetectorService(context);
- }
-
- @Test
- public void testStubbedCall() {
- mTimeZoneDetectorService.stubbedCall();
- }
-}
diff --git a/startop/view_compiler/Android.bp b/startop/view_compiler/Android.bp
index 37caeb2..f5b4308 100644
--- a/startop/view_compiler/Android.bp
+++ b/startop/view_compiler/Android.bp
@@ -58,8 +58,6 @@
"util.cc",
"layout_validation.cc",
],
- // b/123880763, clang-tidy analyzer has segmentation fault with dex_builder.cc
- tidy_checks: ["-clang-analyzer-*"],
host_supported: true,
}
diff --git a/startop/view_compiler/dex_builder.cc b/startop/view_compiler/dex_builder.cc
index 4c1a0dc7..6047e8c 100644
--- a/startop/view_compiler/dex_builder.cc
+++ b/startop/view_compiler/dex_builder.cc
@@ -426,7 +426,7 @@
// Some of the registers don't fit in the four bit short form of the invoke
// instruction, so we need to do an invoke/range. To do this, we need to
// first move all the arguments into contiguous temporary registers.
- std::array<Value, kMaxArgs> scratch{GetScratchRegisters<kMaxArgs>()};
+ std::array<Value, kMaxArgs> scratch = GetScratchRegisters<kMaxArgs>();
const auto& prototype = dex_->GetPrototypeByMethodId(instruction.method_id());
CHECK(prototype.has_value());
diff --git a/telecomm/java/android/telecom/ConferenceParticipant.java b/telecomm/java/android/telecom/ConferenceParticipant.java
index 20b04eb..6317770 100644
--- a/telecomm/java/android/telecom/ConferenceParticipant.java
+++ b/telecomm/java/android/telecom/ConferenceParticipant.java
@@ -19,6 +19,10 @@
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.PhoneConstants;
/**
* Parcelable representation of a participant's state in a conference call.
@@ -27,6 +31,11 @@
public class ConferenceParticipant implements Parcelable {
/**
+ * RFC5767 states that a SIP URI with an unknown number should use an address of
+ * {@code anonymous@anonymous.invalid}. E.g. the host name is anonymous.invalid.
+ */
+ private static final String ANONYMOUS_INVALID_HOST = "anonymous.invalid";
+ /**
* The conference participant's handle (e.g., phone number).
*/
private final Uri mHandle;
@@ -50,6 +59,16 @@
private final int mState;
/**
+ * The connect time of the participant.
+ */
+ private long mConnectTime;
+
+ /**
+ * The connect elapsed time of the participant.
+ */
+ private long mConnectElapsedTime;
+
+ /**
* Creates an instance of {@code ConferenceParticipant}.
*
* @param handle The conference participant's handle (e.g., phone number).
@@ -92,6 +111,54 @@
}
/**
+ * Determines the number presentation for a conference participant. Per RFC5767, if the host
+ * name contains {@code anonymous.invalid} we can assume that there is no valid caller ID
+ * information for the caller, otherwise we'll assume that the URI can be shown.
+ *
+ * @return The number presentation.
+ */
+ @VisibleForTesting
+ public int getParticipantPresentation() {
+ Uri address = getHandle();
+ if (address == null) {
+ return PhoneConstants.PRESENTATION_RESTRICTED;
+ }
+
+ String number = address.getSchemeSpecificPart();
+ // If no number, bail early and set restricted presentation.
+ if (TextUtils.isEmpty(number)) {
+ return PhoneConstants.PRESENTATION_RESTRICTED;
+ }
+ // Per RFC3261, the host name portion can also potentially include extra information:
+ // E.g. sip:anonymous1@anonymous.invalid;legid=1
+ // In this case, hostName will be anonymous.invalid and there is an extra parameter for
+ // legid=1.
+ // Parameters are optional, and the address (e.g. test@test.com) will always be the first
+ // part, with any parameters coming afterwards.
+ String [] hostParts = number.split("[;]");
+ String addressPart = hostParts[0];
+
+ // Get the number portion from the address part.
+ // This will typically be formatted similar to: 6505551212@test.com
+ String [] numberParts = addressPart.split("[@]");
+
+ // If we can't parse the host name out of the URI, then there is probably other data
+ // present, and is likely a valid SIP URI.
+ if (numberParts.length != 2) {
+ return PhoneConstants.PRESENTATION_ALLOWED;
+ }
+ String hostName = numberParts[1];
+
+ // If the hostname portion of the SIP URI is the invalid host string, presentation is
+ // restricted.
+ if (hostName.equals(ANONYMOUS_INVALID_HOST)) {
+ return PhoneConstants.PRESENTATION_RESTRICTED;
+ }
+
+ return PhoneConstants.PRESENTATION_ALLOWED;
+ }
+
+ /**
* Writes the {@code ConferenceParticipant} to a parcel.
*
* @param dest The Parcel in which the object should be written.
@@ -121,6 +188,10 @@
sb.append(Log.pii(mEndpoint));
sb.append(" State: ");
sb.append(Connection.stateToString(mState));
+ sb.append(" ConnectTime: ");
+ sb.append(getConnectTime());
+ sb.append(" ConnectElapsedTime: ");
+ sb.append(getConnectElapsedTime());
sb.append("]");
return sb.toString();
}
@@ -155,4 +226,26 @@
public int getState() {
return mState;
}
+
+ /**
+ * The connect time of the participant to the conference.
+ */
+ public long getConnectTime() {
+ return mConnectTime;
+ }
+
+ public void setConnectTime(long connectTime) {
+ this.mConnectTime = connectTime;
+ }
+
+ /**
+ * The connect elpased time of the participant to the conference.
+ */
+ public long getConnectElapsedTime() {
+ return mConnectElapsedTime;
+ }
+
+ public void setConnectElapsedTime(long connectElapsedTime) {
+ mConnectElapsedTime = connectElapsedTime;
+ }
}
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 05d5a13..bd0d4ae 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -16,10 +16,6 @@
package android.telecom;
-import com.android.internal.os.SomeArgs;
-import com.android.internal.telecom.IVideoCallback;
-import com.android.internal.telecom.IVideoProvider;
-
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
@@ -43,6 +39,10 @@
import android.util.ArraySet;
import android.view.Surface;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.telecom.IVideoCallback;
+import com.android.internal.telecom.IVideoProvider;
+
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
diff --git a/telecomm/java/android/telecom/DisconnectCause.java b/telecomm/java/android/telecom/DisconnectCause.java
index 1de67a5..5a97c94 100644
--- a/telecomm/java/android/telecom/DisconnectCause.java
+++ b/telecomm/java/android/telecom/DisconnectCause.java
@@ -16,9 +16,9 @@
package android.telecom;
+import android.media.ToneGenerator;
import android.os.Parcel;
import android.os.Parcelable;
-import android.media.ToneGenerator;
import android.text.TextUtils;
import java.util.Objects;
@@ -91,6 +91,12 @@
*/
public static final String REASON_IMS_ACCESS_BLOCKED = "REASON_IMS_ACCESS_BLOCKED";
+ /**
+ * Reason code, which indicates that the conference call is simulating single party conference.
+ * @hide
+ */
+ public static final String REASON_EMULATING_SINGLE_CALL = "EMULATING_SINGLE_CALL";
+
private int mDisconnectCode;
private CharSequence mDisconnectLabel;
private CharSequence mDisconnectDescription;
diff --git a/telephony/java/android/provider/Telephony.java b/telephony/java/android/provider/Telephony.java
index fb8f3e7..ac34cea 100644
--- a/telephony/java/android/provider/Telephony.java
+++ b/telephony/java/android/provider/Telephony.java
@@ -2054,6 +2054,608 @@
}
/**
+ * Columns for the "rcs_*" tables used by {@link android.telephony.ims.RcsMessageStore} classes.
+ *
+ * @hide - not meant for public use
+ */
+ public interface RcsColumns {
+ /**
+ * The authority for the content provider
+ */
+ String AUTHORITY = "rcs";
+
+ /**
+ * The URI to start building upon to use {@link com.android.providers.telephony.RcsProvider}
+ */
+ Uri CONTENT_AND_AUTHORITY = Uri.parse("content://" + AUTHORITY);
+
+ /**
+ * The value to be used whenever a transaction that expects an integer to be returned
+ * failed.
+ */
+ int TRANSACTION_FAILED = Integer.MIN_VALUE;
+
+ /**
+ * The value that denotes a timestamp was not set before (e.g. a message that is not
+ * delivered yet will not have a DELIVERED_TIMESTAMP)
+ */
+ long TIMESTAMP_NOT_SET = 0;
+
+ /**
+ * The table that {@link android.telephony.ims.RcsThread} gets persisted to
+ */
+ interface RcsThreadColumns {
+ /**
+ * The path that should be used for referring to
+ * {@link android.telephony.ims.RcsThread}s in
+ * {@link com.android.providers.telephony.RcsProvider} URIs.
+ */
+ String RCS_THREAD_URI_PART = "thread";
+
+ /**
+ * The URI to query or modify {@link android.telephony.ims.RcsThread} via the content
+ * provider.
+ */
+ Uri RCS_THREAD_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY, RCS_THREAD_URI_PART);
+
+ /**
+ * The unique identifier of an {@link android.telephony.ims.RcsThread}
+ */
+ String RCS_THREAD_ID_COLUMN = "rcs_thread_id";
+ }
+
+ /**
+ * The table that {@link android.telephony.ims.Rcs1To1Thread} gets persisted to
+ */
+ interface Rcs1To1ThreadColumns extends RcsThreadColumns {
+ /**
+ * The path that should be used for referring to
+ * {@link android.telephony.ims.Rcs1To1Thread}s in
+ * {@link com.android.providers.telephony.RcsProvider} URIs.
+ */
+ String RCS_1_TO_1_THREAD_URI_PART = "p2p_thread";
+
+ /**
+ * The URI to query or modify {@link android.telephony.ims.Rcs1To1Thread}s via the
+ * content provider
+ */
+ Uri RCS_1_TO_1_THREAD_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY,
+ RCS_1_TO_1_THREAD_URI_PART);
+
+ /**
+ * The SMS/MMS thread to fallback to in case of an RCS outage
+ */
+ String FALLBACK_THREAD_ID_COLUMN = "rcs_fallback_thread_id";
+ }
+
+ /**
+ * The table that {@link android.telephony.ims.RcsGroupThread} gets persisted to
+ */
+ interface RcsGroupThreadColumns extends RcsThreadColumns {
+ /**
+ * The path that should be used for referring to
+ * {@link android.telephony.ims.RcsGroupThread}s in
+ * {@link com.android.providers.telephony.RcsProvider} URIs.
+ */
+ String RCS_GROUP_THREAD_URI_PART = "group_thread";
+
+ /**
+ * The URI to query or modify {@link android.telephony.ims.RcsGroupThread}s via the
+ * content provider
+ */
+ Uri RCS_GROUP_THREAD_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY,
+ RCS_GROUP_THREAD_URI_PART);
+
+ /**
+ * The owner/admin of the {@link android.telephony.ims.RcsGroupThread}
+ */
+ String OWNER_PARTICIPANT_COLUMN = "owner_participant";
+
+ /**
+ * The user visible name of the group
+ */
+ String GROUP_NAME_COLUMN = "group_name";
+
+ /**
+ * The user visible icon of the group
+ */
+ String GROUP_ICON_COLUMN = "group_icon";
+
+ /**
+ * The RCS conference URI for this group
+ */
+ String CONFERENCE_URI_COLUMN = "conference_uri";
+ }
+
+ /**
+ * The view that enables polling from all types of RCS threads at once
+ */
+ interface RcsUnifiedThreadColumns extends RcsThreadColumns, Rcs1To1ThreadColumns,
+ RcsGroupThreadColumns {
+ /**
+ * The type of this {@link android.telephony.ims.RcsThread}
+ */
+ String THREAD_TYPE_COLUMN = "thread_type";
+
+ /**
+ * Integer returned as a result from a database query that denotes the thread is 1 to 1
+ */
+ int THREAD_TYPE_1_TO_1 = 0;
+
+ /**
+ * Integer returned as a result from a database query that denotes the thread is 1 to 1
+ */
+ int THREAD_TYPE_GROUP = 1;
+ }
+
+ /**
+ * The table that {@link android.telephony.ims.RcsParticipant} gets persisted to
+ */
+ interface RcsParticipantColumns {
+ /**
+ * The path that should be used for referring to
+ * {@link android.telephony.ims.RcsParticipant}s in
+ * {@link com.android.providers.telephony.RcsProvider} URIs.
+ */
+ String RCS_PARTICIPANT_URI_PART = "participant";
+
+ /**
+ * The URI to query or modify {@link android.telephony.ims.RcsParticipant}s via the
+ * content provider
+ */
+ Uri RCS_PARTICIPANT_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY,
+ RCS_PARTICIPANT_URI_PART);
+
+ /**
+ * The unique identifier of the entry in the database
+ */
+ String RCS_PARTICIPANT_ID_COLUMN = "rcs_participant_id";
+
+ /**
+ * A foreign key on canonical_address table, also used by SMS/MMS
+ */
+ String CANONICAL_ADDRESS_ID_COLUMN = "canonical_address_id";
+
+ /**
+ * The user visible RCS alias for this participant.
+ */
+ String RCS_ALIAS_COLUMN = "rcs_alias";
+ }
+
+ /**
+ * Additional constants to enable access to {@link android.telephony.ims.RcsParticipant}
+ * related data
+ */
+ interface RcsParticipantHelpers extends RcsParticipantColumns {
+ /**
+ * The view that unifies "rcs_participant" and "canonical_addresses" tables for easy
+ * access to participant address.
+ */
+ String RCS_PARTICIPANT_WITH_ADDRESS_VIEW = "rcs_participant_with_address_view";
+
+ /**
+ * The view that unifies "rcs_participant", "canonical_addresses" and
+ * "rcs_thread_participant" junction table to get full information on participants that
+ * contribute to threads.
+ */
+ String RCS_PARTICIPANT_WITH_THREAD_VIEW = "rcs_participant_with_thread_view";
+ }
+
+ /**
+ * The table that {@link android.telephony.ims.RcsMessage} gets persisted to
+ */
+ interface RcsMessageColumns {
+ /**
+ * Denotes the type of this message (i.e.
+ * {@link android.telephony.ims.RcsIncomingMessage} or
+ * {@link android.telephony.ims.RcsOutgoingMessage}
+ */
+ String MESSAGE_TYPE_COLUMN = "rcs_message_type";
+
+ /**
+ * The unique identifier for the message in the database - i.e. the primary key.
+ */
+ String MESSAGE_ID_COLUMN = "rcs_message_row_id";
+
+ /**
+ * The globally unique RCS identifier for the message. Please see 4.4.5.2 - GSMA
+ * RCC.53 (RCS Device API 1.6 Specification)
+ */
+ String GLOBAL_ID_COLUMN = "rcs_message_global_id";
+
+ /**
+ * The subscription where this message was sent from/to.
+ */
+ String SUB_ID_COLUMN = "sub_id";
+
+ /**
+ * The sending status of the message.
+ * @see android.telephony.ims.RcsMessage.RcsMessageStatus
+ */
+ String STATUS_COLUMN = "status";
+
+ /**
+ * The creation timestamp of the message.
+ */
+ String ORIGINATION_TIMESTAMP_COLUMN = "origination_timestamp";
+
+ /**
+ * The text content of the message.
+ */
+ String MESSAGE_TEXT_COLUMN = "rcs_text";
+
+ /**
+ * The latitude content of the message, if it contains a location.
+ */
+ String LATITUDE_COLUMN = "latitude";
+
+ /**
+ * The longitude content of the message, if it contains a location.
+ */
+ String LONGITUDE_COLUMN = "longitude";
+ }
+
+ /**
+ * The table that additional information of {@link android.telephony.ims.RcsIncomingMessage}
+ * gets persisted to.
+ */
+ interface RcsIncomingMessageColumns extends RcsMessageColumns {
+ /**
+ The path that should be used for referring to
+ * {@link android.telephony.ims.RcsIncomingMessage}s in
+ * {@link com.android.providers.telephony.RcsProvider} URIs.
+ */
+ String INCOMING_MESSAGE_URI_PART = "incoming_message";
+
+ /**
+ * The URI to query incoming messages through
+ * {@link com.android.providers.telephony.RcsProvider}
+ */
+ Uri INCOMING_MESSAGE_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY,
+ INCOMING_MESSAGE_URI_PART);
+
+ /**
+ * The ID of the {@link android.telephony.ims.RcsParticipant} that sent this message
+ */
+ String SENDER_PARTICIPANT_ID_COLUMN = "sender_participant";
+
+ /**
+ * The timestamp of arrival for this message.
+ */
+ String ARRIVAL_TIMESTAMP_COLUMN = "arrival_timestamp";
+
+ /**
+ * The time when the recipient has read this message.
+ */
+ String SEEN_TIMESTAMP_COLUMN = "seen_timestamp";
+ }
+
+ /**
+ * The table that additional information of {@link android.telephony.ims.RcsOutgoingMessage}
+ * gets persisted to.
+ */
+ interface RcsOutgoingMessageColumns extends RcsMessageColumns {
+ /**
+ * The path that should be used for referring to
+ * {@link android.telephony.ims.RcsOutgoingMessage}s in
+ * {@link com.android.providers.telephony.RcsProvider} URIs.
+ */
+ String OUTGOING_MESSAGE_URI_PART = "outgoing_message";
+
+ /**
+ * The URI to query or modify {@link android.telephony.ims.RcsOutgoingMessage}s via the
+ * content provider
+ */
+ Uri OUTGOING_MESSAGE_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY,
+ OUTGOING_MESSAGE_URI_PART);
+ }
+
+ /**
+ * The delivery information of an {@link android.telephony.ims.RcsOutgoingMessage}
+ */
+ interface RcsMessageDeliveryColumns extends RcsOutgoingMessageColumns {
+ /**
+ * The path that should be used for referring to
+ * {@link android.telephony.ims.RcsOutgoingMessageDelivery}s in
+ * {@link com.android.providers.telephony.RcsProvider} URIs.
+ */
+ String DELIVERY_URI_PART = "delivery";
+
+ /**
+ * The timestamp of delivery of this message.
+ */
+ String DELIVERED_TIMESTAMP_COLUMN = "delivered_timestamp";
+
+ /**
+ * The time when the recipient has read this message.
+ */
+ String SEEN_TIMESTAMP_COLUMN = "seen_timestamp";
+ }
+
+ /**
+ * The views that allow querying {@link android.telephony.ims.RcsIncomingMessage} and
+ * {@link android.telephony.ims.RcsOutgoingMessage} at the same time.
+ */
+ interface RcsUnifiedMessageColumns extends RcsIncomingMessageColumns,
+ RcsOutgoingMessageColumns {
+ /**
+ * The path that is used to query all {@link android.telephony.ims.RcsMessage} in
+ * {@link com.android.providers.telephony.RcsProvider} URIs.
+ */
+ String UNIFIED_MESSAGE_URI_PART = "message";
+
+ /**
+ * The URI to query all types of {@link android.telephony.ims.RcsMessage}s
+ */
+ Uri UNIFIED_MESSAGE_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY,
+ UNIFIED_MESSAGE_URI_PART);
+
+ /**
+ * The name of the view that unites rcs_message and rcs_incoming_message tables.
+ */
+ String UNIFIED_INCOMING_MESSAGE_VIEW = "unified_incoming_message_view";
+
+ /**
+ * The name of the view that unites rcs_message and rcs_outgoing_message tables.
+ */
+ String UNIFIED_OUTGOING_MESSAGE_VIEW = "unified_outgoing_message_view";
+
+ /**
+ * The column that shows from which table the message entry came from.
+ */
+ String MESSAGE_TYPE_COLUMN = "message_type";
+
+ /**
+ * Integer returned as a result from a database query that denotes that the message is
+ * an incoming message
+ */
+ int MESSAGE_TYPE_INCOMING = 1;
+
+ /**
+ * Integer returned as a result from a database query that denotes that the message is
+ * an outgoing message
+ */
+ int MESSAGE_TYPE_OUTGOING = 0;
+ }
+
+ /**
+ * The table that {@link android.telephony.ims.RcsFileTransferPart} gets persisted to.
+ */
+ interface RcsFileTransferColumns {
+ /**
+ * The path that should be used for referring to
+ * {@link android.telephony.ims.RcsFileTransferPart}s in
+ * {@link com.android.providers.telephony.RcsProvider} URIs.
+ */
+ String FILE_TRANSFER_URI_PART = "file_transfer";
+
+ /**
+ * The URI to query or modify {@link android.telephony.ims.RcsFileTransferPart}s via the
+ * content provider
+ */
+ Uri FILE_TRANSFER_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY,
+ FILE_TRANSFER_URI_PART);
+
+ /**
+ * The globally unique file transfer ID for this RCS file transfer.
+ */
+ String FILE_TRANSFER_ID_COLUMN = "rcs_file_transfer_id";
+
+ /**
+ * The RCS session ID for this file transfer. The ID is implementation dependent but
+ * should be unique.
+ */
+ String SESSION_ID_COLUMN = "session_id";
+
+ /**
+ * The URI that points to the content of this file transfer
+ */
+ String CONTENT_URI_COLUMN = "content_uri";
+
+ /**
+ * The file type of this file transfer in bytes. The validity of types is not enforced
+ * in {@link android.telephony.ims.RcsMessageStore} APIs.
+ */
+ String CONTENT_TYPE_COLUMN = "content_type";
+
+ /**
+ * The size of the file transfer in bytes.
+ */
+ String FILE_SIZE_COLUMN = "file_size";
+
+ /**
+ * Number of bytes that was successfully transmitted for this file transfer
+ */
+ String SUCCESSFULLY_TRANSFERRED_BYTES = "transfer_offset";
+
+ /**
+ * The status of this file transfer
+ * @see android.telephony.ims.RcsFileTransferPart.RcsFileTransferStatus
+ */
+ String TRANSFER_STATUS_COLUMN = "transfer_status";
+
+ /**
+ * The on-screen width of the file transfer, if it contains multi-media
+ */
+ String WIDTH_COLUMN = "width";
+
+ /**
+ * The on-screen height of the file transfer, if it contains multi-media
+ */
+ String HEIGHT_COLUMN = "height";
+
+ /**
+ * The duration of the content in milliseconds if this file transfer contains
+ * multi-media
+ */
+ String DURATION_MILLIS_COLUMN = "duration";
+
+ /**
+ * The URI to the preview of the content of this file transfer
+ */
+ String PREVIEW_URI_COLUMN = "preview_uri";
+
+ /**
+ * The type of the preview of the content of this file transfer. The validity of types
+ * is not enforced in {@link android.telephony.ims.RcsMessageStore} APIs.
+ */
+ String PREVIEW_TYPE_COLUMN = "preview_type";
+ }
+
+ /**
+ * The table that holds the information for
+ * {@link android.telephony.ims.RcsGroupThreadEvent} and its subclasses.
+ */
+ interface RcsThreadEventColumns {
+ /**
+ * The string used in the {@link com.android.providers.telephony.RcsProvider} URI to
+ * refer to participant joined events (example URI:
+ * {@code content://rcs/group_thread/3/participant_joined_event})
+ */
+ String PARTICIPANT_JOINED_URI_PART = "participant_joined_event";
+
+ /**
+ * The string used in the {@link com.android.providers.telephony.RcsProvider} URI to
+ * refer to participant left events. (example URI:
+ * {@code content://rcs/group_thread/3/participant_left_event/4})
+ */
+ String PARTICIPANT_LEFT_URI_PART = "participant_left_event";
+
+ /**
+ * The string used in the {@link com.android.providers.telephony.RcsProvider} URI to
+ * refer to name changed events. (example URI:
+ * {@code content://rcs/group_thread/3/name_changed_event})
+ */
+ String NAME_CHANGED_URI_PART = "name_changed_event";
+
+ /**
+ * The string used in the {@link com.android.providers.telephony.RcsProvider} URI to
+ * refer to icon changed events. (example URI:
+ * {@code content://rcs/group_thread/3/icon_changed_event})
+ */
+ String ICON_CHANGED_URI_PART = "icon_changed_event";
+
+ /**
+ * The unique ID of this event in the database, i.e. the primary key
+ */
+ String EVENT_ID_COLUMN = "event_id";
+
+ /**
+ * The type of this event
+ *
+ * @see RcsEventTypes
+ */
+ String EVENT_TYPE_COLUMN = "event_type";
+
+ /**
+ * The timestamp in milliseconds of when this event happened
+ */
+ String TIMESTAMP_COLUMN = "origination_timestamp";
+
+ /**
+ * The participant that generated this event
+ */
+ String SOURCE_PARTICIPANT_ID_COLUMN = "source_participant";
+
+ /**
+ * The receiving participant of this event if this was an
+ * {@link android.telephony.ims.RcsGroupThreadParticipantJoinedEvent} or
+ * {@link android.telephony.ims.RcsGroupThreadParticipantLeftEvent}
+ */
+ String DESTINATION_PARTICIPANT_ID_COLUMN = "destination_participant";
+
+ /**
+ * The URI for the new icon of the group thread if this was an
+ * {@link android.telephony.ims.RcsGroupThreadIconChangedEvent}
+ */
+ String NEW_ICON_URI_COLUMN = "new_icon_uri";
+
+ /**
+ * The URI for the new name of the group thread if this was an
+ * {@link android.telephony.ims.RcsGroupThreadNameChangedEvent}
+ */
+ String NEW_NAME_COLUMN = "new_name";
+ }
+
+ /**
+ * The table that {@link android.telephony.ims.RcsParticipantAliasChangedEvent} gets
+ * persisted to
+ */
+ interface RcsParticipantEventColumns {
+ /**
+ * The path that should be used for referring to
+ * {@link android.telephony.ims.RcsParticipantAliasChangedEvent}s in
+ * {@link com.android.providers.telephony.RcsProvider} URIs.
+ */
+ String ALIAS_CHANGE_EVENT_URI_PART = "alias_change_event";
+
+ /**
+ * The new alias of the participant
+ */
+ String NEW_ALIAS_COLUMN = "new_alias";
+ }
+
+ /**
+ * These values are used in {@link com.android.providers.telephony.RcsProvider} to determine
+ * what kind of event is present in the storage.
+ */
+ interface RcsEventTypes {
+ /**
+ * Integer constant that is stored in the
+ * {@link com.android.providers.telephony.RcsProvider} database that denotes the event
+ * is of type {@link android.telephony.ims.RcsParticipantAliasChangedEvent}
+ */
+ int PARTICIPANT_ALIAS_CHANGED_EVENT_TYPE = 1;
+
+ /**
+ * Integer constant that is stored in the
+ * {@link com.android.providers.telephony.RcsProvider} database that denotes the event
+ * is of type {@link android.telephony.ims.RcsGroupThreadParticipantJoinedEvent}
+ */
+ int PARTICIPANT_JOINED_EVENT_TYPE = 2;
+
+ /**
+ * Integer constant that is stored in the
+ * {@link com.android.providers.telephony.RcsProvider} database that denotes the event
+ * is of type {@link android.telephony.ims.RcsGroupThreadParticipantLeftEvent}
+ */
+ int PARTICIPANT_LEFT_EVENT_TYPE = 4;
+
+ /**
+ * Integer constant that is stored in the
+ * {@link com.android.providers.telephony.RcsProvider} database that denotes the event
+ * is of type {@link android.telephony.ims.RcsGroupThreadIconChangedEvent}
+ */
+ int ICON_CHANGED_EVENT_TYPE = 8;
+
+ /**
+ * Integer constant that is stored in the
+ * {@link com.android.providers.telephony.RcsProvider} database that denotes the event
+ * is of type {@link android.telephony.ims.RcsGroupThreadNameChangedEvent}
+ */
+ int NAME_CHANGED_EVENT_TYPE = 16;
+ }
+
+ /**
+ * The view that allows unified querying across all events
+ */
+ interface RcsUnifiedEventHelper extends RcsParticipantEventColumns, RcsThreadEventColumns {
+ /**
+ * The path that should be used for referring to
+ * {@link android.telephony.ims.RcsEvent}s in
+ * {@link com.android.providers.telephony.RcsProvider} URIs.
+ */
+ String RCS_EVENT_QUERY_URI_PATH = "event";
+
+ /**
+ * The URI to query {@link android.telephony.ims.RcsEvent}s via the content provider.
+ */
+ Uri RCS_EVENT_QUERY_URI = Uri.withAppendedPath(CONTENT_AND_AUTHORITY,
+ RCS_EVENT_QUERY_URI_PATH);
+ }
+ }
+
+ /**
* Contains all MMS messages.
*/
public static final class Mms implements BaseMmsColumns {
diff --git a/telephony/java/android/telephony/ims/Rcs1To1Thread.java b/telephony/java/android/telephony/ims/Rcs1To1Thread.java
index 709b3aa..cc28ee0 100644
--- a/telephony/java/android/telephony/ims/Rcs1To1Thread.java
+++ b/telephony/java/android/telephony/ims/Rcs1To1Thread.java
@@ -15,42 +15,72 @@
*/
package android.telephony.ims;
-import android.os.Parcel;
+import android.annotation.NonNull;
+import android.annotation.WorkerThread;
/**
* Rcs1To1Thread represents a single RCS conversation thread with a total of two
- * {@link RcsParticipant}s.
- * @hide - TODO(sahinc) make this public
+ * {@link RcsParticipant}s. Please see Section 5 (1-to-1 Messaging) - GSMA RCC.71 (RCS Universal
+ * Profile Service Definition Document)
+ *
+ * @hide - TODO(109759350) make this public
*/
public class Rcs1To1Thread extends RcsThread {
+ private int mThreadId;
+
+ /**
+ * Public constructor only for RcsMessageStoreController to initialize new threads.
+ *
+ * @hide
+ */
public Rcs1To1Thread(int threadId) {
super(threadId);
+ mThreadId = threadId;
}
- public static final Creator<Rcs1To1Thread> CREATOR = new Creator<Rcs1To1Thread>() {
- @Override
- public Rcs1To1Thread createFromParcel(Parcel in) {
- return new Rcs1To1Thread(in);
- }
-
- @Override
- public Rcs1To1Thread[] newArray(int size) {
- return new Rcs1To1Thread[size];
- }
- };
-
- protected Rcs1To1Thread(Parcel in) {
- super(in);
- }
-
+ /**
+ * @return Returns {@code false} as this is always a 1 to 1 thread.
+ */
@Override
- public int describeContents() {
- return 0;
+ public boolean isGroup() {
+ return false;
}
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(RCS_1_TO_1_TYPE);
- super.writeToParcel(dest, flags);
+ /**
+ * {@link Rcs1To1Thread}s can fall back to SMS as a back-up protocol. This function returns the
+ * thread id to be used to query {@code content://mms-sms/conversation/#} to get the fallback
+ * thread.
+ *
+ * @return The thread id to be used to query the mms-sms authority
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @WorkerThread
+ public long getFallbackThreadId() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.get1To1ThreadFallbackThreadId(mThreadId));
+ }
+
+ /**
+ * If the RCS client allows falling back to SMS, it needs to create an MMS-SMS thread in the
+ * SMS/MMS Provider( see {@link android.provider.Telephony.MmsSms#CONTENT_CONVERSATIONS_URI}.
+ * Use this function to link the {@link Rcs1To1Thread} to the MMS-SMS thread. This function
+ * also updates the storage.
+ *
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setFallbackThreadId(long fallbackThreadId) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(
+ iRcs -> iRcs.set1To1ThreadFallbackThreadId(mThreadId, fallbackThreadId));
+ }
+
+ /**
+ * @return Returns the {@link RcsParticipant} that receives the messages sent in this thread.
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @NonNull
+ @WorkerThread
+ public RcsParticipant getRecipient() throws RcsMessageStoreException {
+ return new RcsParticipant(
+ RcsControllerCall.call(iRcs -> iRcs.get1To1ThreadOtherParticipantId(mThreadId)));
}
}
diff --git a/telephony/java/android/telephony/ims/RcsControllerCall.java b/telephony/java/android/telephony/ims/RcsControllerCall.java
new file mode 100644
index 0000000..5512c4c
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsControllerCall.java
@@ -0,0 +1,64 @@
+/*
+ * 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.telephony.ims;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.telephony.ims.aidl.IRcs;
+
+/**
+ * A wrapper class around RPC calls that {@link RcsMessageStore} APIs to minimize boilerplate code.
+ *
+ * @hide - not meant for public use
+ */
+class RcsControllerCall {
+ static <R> R call(RcsServiceCall<R> serviceCall) throws RcsMessageStoreException {
+ IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_RCS_SERVICE));
+ if (iRcs == null) {
+ throw new RcsMessageStoreException("Could not connect to RCS storage service");
+ }
+
+ try {
+ return serviceCall.methodOnIRcs(iRcs);
+ } catch (RemoteException exception) {
+ throw new RcsMessageStoreException(exception.getMessage());
+ }
+ }
+
+ static void callWithNoReturn(RcsServiceCallWithNoReturn serviceCall)
+ throws RcsMessageStoreException {
+ IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_RCS_SERVICE));
+ if (iRcs == null) {
+ throw new RcsMessageStoreException("Could not connect to RCS storage service");
+ }
+
+ try {
+ serviceCall.methodOnIRcs(iRcs);
+ } catch (RemoteException exception) {
+ throw new RcsMessageStoreException(exception.getMessage());
+ }
+ }
+
+ interface RcsServiceCall<R> {
+ R methodOnIRcs(IRcs iRcs) throws RemoteException;
+ }
+
+ interface RcsServiceCallWithNoReturn {
+ void methodOnIRcs(IRcs iRcs) throws RemoteException;
+ }
+}
diff --git a/telephony/java/android/telephony/ims/RcsPart.aidl b/telephony/java/android/telephony/ims/RcsEvent.aidl
similarity index 96%
rename from telephony/java/android/telephony/ims/RcsPart.aidl
rename to telephony/java/android/telephony/ims/RcsEvent.aidl
index 8b8077d..08974e0 100644
--- a/telephony/java/android/telephony/ims/RcsPart.aidl
+++ b/telephony/java/android/telephony/ims/RcsEvent.aidl
@@ -17,4 +17,4 @@
package android.telephony.ims;
-parcelable RcsPart;
+parcelable RcsEvent;
diff --git a/telephony/java/android/telephony/ims/RcsEvent.java b/telephony/java/android/telephony/ims/RcsEvent.java
new file mode 100644
index 0000000..744ac76
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsEvent.java
@@ -0,0 +1,60 @@
+/*
+ * 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.telephony.ims;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The base class for events that can happen on {@link RcsParticipant}s and {@link RcsThread}s.
+ * @hide - TODO(109759350) make this public
+ */
+public abstract class RcsEvent implements Parcelable {
+ protected long mTimestamp;
+
+ protected RcsEvent(long timestamp) {
+ mTimestamp = timestamp;
+ }
+
+ /**
+ * @return Returns the time of when this event happened. The timestamp is defined as
+ * milliseconds passed after midnight, January 1, 1970 UTC
+ */
+ public long getTimestamp() {
+ return mTimestamp;
+ }
+
+ /**
+ * Persists the event to the data store
+ *
+ * @hide
+ */
+ abstract void persist() throws RcsMessageStoreException;
+
+ RcsEvent(Parcel in) {
+ mTimestamp = in.readLong();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(mTimestamp);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+}
diff --git a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl b/telephony/java/android/telephony/ims/RcsEventQueryParameters.aidl
similarity index 94%
rename from telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
rename to telephony/java/android/telephony/ims/RcsEventQueryParameters.aidl
index 82d985d..9a3600b 100644
--- a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
+++ b/telephony/java/android/telephony/ims/RcsEventQueryParameters.aidl
@@ -17,4 +17,4 @@
package android.telephony.ims;
-parcelable RcsThreadIconChangedEvent;
+parcelable RcsEventQueryParameters;
diff --git a/telephony/java/android/telephony/ims/RcsEventQueryParameters.java b/telephony/java/android/telephony/ims/RcsEventQueryParameters.java
new file mode 100644
index 0000000..6aee56f
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsEventQueryParameters.java
@@ -0,0 +1,322 @@
+/*
+ * 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.telephony.ims;
+
+import static android.provider.Telephony.RcsColumns.RcsEventTypes.ICON_CHANGED_EVENT_TYPE;
+import static android.provider.Telephony.RcsColumns.RcsEventTypes.NAME_CHANGED_EVENT_TYPE;
+import static android.provider.Telephony.RcsColumns.RcsEventTypes.PARTICIPANT_ALIAS_CHANGED_EVENT_TYPE;
+import static android.provider.Telephony.RcsColumns.RcsEventTypes.PARTICIPANT_JOINED_EVENT_TYPE;
+import static android.provider.Telephony.RcsColumns.RcsEventTypes.PARTICIPANT_LEFT_EVENT_TYPE;
+
+import android.annotation.CheckResult;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.security.InvalidParameterException;
+
+/**
+ * The parameters to pass into
+ * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)} in order to select a
+ * subset of {@link RcsEvent}s present in the message store.
+ *
+ * @hide TODO - make the Builder and builder() public. The rest should stay internal only.
+ */
+public class RcsEventQueryParameters implements Parcelable {
+ /**
+ * Flag to be used with {@link Builder#setEventType(int)} to make
+ * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)} return all types of
+ * {@link RcsEvent}s
+ */
+ public static final int ALL_EVENTS = -1;
+
+ /**
+ * Flag to be used with {@link Builder#setEventType(int)} to make
+ * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)} return sub-types of
+ * {@link RcsGroupThreadEvent}s
+ */
+ public static final int ALL_GROUP_THREAD_EVENTS = 0;
+
+ /**
+ * Flag to be used with {@link Builder#setEventType(int)} to make
+ * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)} return only
+ * {@link RcsParticipantAliasChangedEvent}s
+ */
+ public static final int PARTICIPANT_ALIAS_CHANGED_EVENT =
+ PARTICIPANT_ALIAS_CHANGED_EVENT_TYPE;
+
+ /**
+ * Flag to be used with {@link Builder#setEventType(int)} to make
+ * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)} return only
+ * {@link RcsGroupThreadParticipantJoinedEvent}s
+ */
+ public static final int GROUP_THREAD_PARTICIPANT_JOINED_EVENT =
+ PARTICIPANT_JOINED_EVENT_TYPE;
+
+ /**
+ * Flag to be used with {@link Builder#setEventType(int)} to make
+ * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)} return only
+ * {@link RcsGroupThreadParticipantLeftEvent}s
+ */
+ public static final int GROUP_THREAD_PARTICIPANT_LEFT_EVENT =
+ PARTICIPANT_LEFT_EVENT_TYPE;
+
+ /**
+ * Flag to be used with {@link Builder#setEventType(int)} to make
+ * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)} return only
+ * {@link RcsGroupThreadNameChangedEvent}s
+ */
+ public static final int GROUP_THREAD_NAME_CHANGED_EVENT = NAME_CHANGED_EVENT_TYPE;
+
+ /**
+ * Flag to be used with {@link Builder#setEventType(int)} to make
+ * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)} return only
+ * {@link RcsGroupThreadIconChangedEvent}s
+ */
+ public static final int GROUP_THREAD_ICON_CHANGED_EVENT = ICON_CHANGED_EVENT_TYPE;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({ALL_EVENTS, ALL_GROUP_THREAD_EVENTS, PARTICIPANT_ALIAS_CHANGED_EVENT,
+ GROUP_THREAD_PARTICIPANT_JOINED_EVENT, GROUP_THREAD_PARTICIPANT_LEFT_EVENT,
+ GROUP_THREAD_NAME_CHANGED_EVENT, GROUP_THREAD_ICON_CHANGED_EVENT})
+ public @interface EventType {
+ }
+
+ /**
+ * Flag to be used with {@link Builder#setSortProperty(int)} that makes the result set sorted
+ * in the order of creation for faster query results.
+ */
+ public static final int SORT_BY_CREATION_ORDER = 0;
+
+ /**
+ * Flag to be used with {@link Builder#setSortProperty(int)} that makes the result set sorted
+ * with respect to {@link RcsEvent#getTimestamp()}
+ */
+ public static final int SORT_BY_TIMESTAMP = 1;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SORT_BY_CREATION_ORDER, SORT_BY_TIMESTAMP})
+ public @interface SortingProperty {
+ }
+
+ /**
+ * The key to pass into a Bundle, for usage in RcsProvider.query(Bundle)
+ * @hide - not meant for public use
+ */
+ public static final String EVENT_QUERY_PARAMETERS_KEY = "event_query_parameters";
+
+ // Which types of events the results should be limited to
+ private @EventType int mEventType;
+ // The property which the results should be sorted against
+ private int mSortingProperty;
+ // Whether the results should be sorted in ascending order
+ private boolean mIsAscending;
+ // The number of results that should be returned with this query
+ private int mLimit;
+ // The thread that the results are limited to
+ private int mThreadId;
+
+ RcsEventQueryParameters(@EventType int eventType, int threadId,
+ @SortingProperty int sortingProperty, boolean isAscending, int limit) {
+ mEventType = eventType;
+ mSortingProperty = sortingProperty;
+ mIsAscending = isAscending;
+ mLimit = limit;
+ mThreadId = threadId;
+ }
+
+ /**
+ * @return Returns the type of {@link RcsEvent}s that this {@link RcsEventQueryParameters} is
+ * set to query for.
+ */
+ public @EventType int getEventType() {
+ return mEventType;
+ }
+
+ /**
+ * @return Returns the type of {@link RcsEvent}s that this {@link RcsEventQueryParameters} is
+ * set to query for.
+ */
+ public int getLimit() {
+ return mLimit;
+ }
+
+ /**
+ * @return Returns the property where the results should be sorted against.
+ * @see SortingProperty
+ */
+ public int getSortingProperty() {
+ return mSortingProperty;
+ }
+
+ /**
+ * @return Returns {@code true} if the result set will be sorted in ascending order,
+ * {@code false} if it will be sorted in descending order.
+ */
+ public boolean getSortDirection() {
+ return mIsAscending;
+ }
+
+ /**
+ * @return Returns the ID of the {@link RcsGroupThread} that the results are limited to. As this
+ * API exposes an ID, it should stay hidden.
+ *
+ * @hide
+ */
+ public int getThreadId() {
+ return mThreadId;
+ }
+
+ /**
+ * A helper class to build the {@link RcsEventQueryParameters}.
+ */
+ public static class Builder {
+ private @EventType int mEventType;
+ private @SortingProperty int mSortingProperty;
+ private boolean mIsAscending;
+ private int mLimit = 100;
+ private int mThreadId;
+
+ /**
+ * Creates a new builder for {@link RcsEventQueryParameters} to be used in
+ * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)}
+ */
+ public Builder() {
+ // empty implementation
+ }
+
+ /**
+ * Desired number of events to be returned from the query. Passing in 0 will return all
+ * existing events at once. The limit defaults to 100.
+ *
+ * @param limit The number to limit the query result to.
+ * @return The same instance of the builder to chain parameters.
+ * @throws InvalidParameterException If the given limit is negative.
+ */
+ @CheckResult
+ public Builder setResultLimit(@IntRange(from = 0) int limit)
+ throws InvalidParameterException {
+ if (limit < 0) {
+ throw new InvalidParameterException("The query limit must be non-negative");
+ }
+
+ mLimit = limit;
+ return this;
+ }
+
+ /**
+ * Sets the type of events to be returned from the query.
+ *
+ * @param eventType The type of event to be returned.
+ * @return The same instance of the builder to chain parameters.
+ */
+ @CheckResult
+ public Builder setEventType(@EventType int eventType) {
+ mEventType = eventType;
+ return this;
+ }
+
+ /**
+ * Sets the property where the results should be sorted against. Defaults to
+ * {@link RcsEventQueryParameters.SortingProperty#SORT_BY_CREATION_ORDER}
+ *
+ * @param sortingProperty against which property the results should be sorted
+ * @return The same instance of the builder to chain parameters.
+ */
+ @CheckResult
+ public Builder setSortProperty(@SortingProperty int sortingProperty) {
+ mSortingProperty = sortingProperty;
+ return this;
+ }
+
+ /**
+ * Sets whether the results should be sorted ascending or descending
+ *
+ * @param isAscending whether the results should be sorted ascending
+ * @return The same instance of the builder to chain parameters.
+ */
+ @CheckResult
+ public Builder setSortDirection(boolean isAscending) {
+ mIsAscending = isAscending;
+ return this;
+ }
+
+ /**
+ * Limits the results to the given {@link RcsGroupThread}. Setting this value prevents
+ * returning any instances of {@link RcsParticipantAliasChangedEvent}.
+ *
+ * @param groupThread The thread to limit the results to.
+ * @return The same instance of the builder to chain parameters.
+ */
+ @CheckResult
+ public Builder setGroupThread(@NonNull RcsGroupThread groupThread) {
+ mThreadId = groupThread.getThreadId();
+ return this;
+ }
+
+ /**
+ * Builds the {@link RcsEventQueryParameters} to use in
+ * {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)}
+ *
+ * @return An instance of {@link RcsEventQueryParameters} to use with the event query.
+ */
+ public RcsEventQueryParameters build() {
+ return new RcsEventQueryParameters(mEventType, mThreadId, mSortingProperty,
+ mIsAscending, mLimit);
+ }
+ }
+
+ protected RcsEventQueryParameters(Parcel in) {
+ mEventType = in.readInt();
+ mThreadId = in.readInt();
+ mSortingProperty = in.readInt();
+ mIsAscending = in.readBoolean();
+ mLimit = in.readInt();
+ }
+
+ public static final Creator<RcsEventQueryParameters> CREATOR =
+ new Creator<RcsEventQueryParameters>() {
+ @Override
+ public RcsEventQueryParameters createFromParcel(Parcel in) {
+ return new RcsEventQueryParameters(in);
+ }
+
+ @Override
+ public RcsEventQueryParameters[] newArray(int size) {
+ return new RcsEventQueryParameters[size];
+ }
+ };
+
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mEventType);
+ dest.writeInt(mThreadId);
+ dest.writeInt(mSortingProperty);
+ dest.writeBoolean(mIsAscending);
+ dest.writeInt(mLimit);
+ }
+}
diff --git a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl b/telephony/java/android/telephony/ims/RcsEventQueryResult.aidl
similarity index 94%
copy from telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
copy to telephony/java/android/telephony/ims/RcsEventQueryResult.aidl
index 82d985d..7d13335 100644
--- a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
+++ b/telephony/java/android/telephony/ims/RcsEventQueryResult.aidl
@@ -17,4 +17,4 @@
package android.telephony.ims;
-parcelable RcsThreadIconChangedEvent;
+parcelable RcsEventQueryResult;
diff --git a/telephony/java/android/telephony/ims/RcsEventQueryResult.java b/telephony/java/android/telephony/ims/RcsEventQueryResult.java
new file mode 100644
index 0000000..27898ab
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsEventQueryResult.java
@@ -0,0 +1,90 @@
+/*
+ * 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.telephony.ims;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * The result of a {@link RcsMessageStore#getRcsEvents(RcsEventQueryParameters)}
+ * call. This class allows getting the token for querying the next batch of events in order to
+ * prevent handling large amounts of data at once.
+ *
+ * @hide
+ */
+public class RcsEventQueryResult implements Parcelable {
+ private RcsQueryContinuationToken mContinuationToken;
+ private List<RcsEvent> mEvents;
+
+ /**
+ * Internal constructor for {@link com.android.internal.telephony.ims.RcsMessageStoreController}
+ * to create query results
+ *
+ * @hide
+ */
+ public RcsEventQueryResult(
+ RcsQueryContinuationToken continuationToken,
+ List<RcsEvent> events) {
+ mContinuationToken = continuationToken;
+ mEvents = events;
+ }
+
+ /**
+ * Returns a token to call
+ * {@link RcsMessageStore#getRcsEvents(RcsQueryContinuationToken)}
+ * to get the next batch of {@link RcsEvent}s.
+ */
+ public RcsQueryContinuationToken getContinuationToken() {
+ return mContinuationToken;
+ }
+
+ /**
+ * Returns all the {@link RcsEvent}s in the current query result. Call {@link
+ * RcsMessageStore#getRcsEvents(RcsQueryContinuationToken)} to get the next batch
+ * of {@link RcsEvent}s.
+ */
+ public List<RcsEvent> getEvents() {
+ return mEvents;
+ }
+
+ protected RcsEventQueryResult(Parcel in) {
+ }
+
+ public static final Creator<RcsEventQueryResult> CREATOR = new Creator<RcsEventQueryResult>() {
+ @Override
+ public RcsEventQueryResult createFromParcel(Parcel in) {
+ return new RcsEventQueryResult(in);
+ }
+
+ @Override
+ public RcsEventQueryResult[] newArray(int size) {
+ return new RcsEventQueryResult[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mContinuationToken, flags);
+ }
+}
diff --git a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl b/telephony/java/android/telephony/ims/RcsFileTransferCreationParameters.aidl
similarity index 93%
copy from telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
copy to telephony/java/android/telephony/ims/RcsFileTransferCreationParameters.aidl
index 82d985d..5fec021 100644
--- a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
+++ b/telephony/java/android/telephony/ims/RcsFileTransferCreationParameters.aidl
@@ -17,4 +17,4 @@
package android.telephony.ims;
-parcelable RcsThreadIconChangedEvent;
+parcelable RcsFileTransferCreationParameters;
diff --git a/telephony/java/android/telephony/ims/RcsFileTransferCreationParameters.java b/telephony/java/android/telephony/ims/RcsFileTransferCreationParameters.java
new file mode 100644
index 0000000..bd7cb4b
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsFileTransferCreationParameters.java
@@ -0,0 +1,360 @@
+/*
+ * 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.telephony.ims;
+
+import android.annotation.CheckResult;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Pass an instance of this class to
+ * {@link RcsMessage#insertFileTransfer(RcsFileTransferCreationParameters)} create an
+ * {@link RcsFileTransferPart} and save it into storage.
+ *
+ * @hide - TODO(109759350) make this public
+ */
+public class RcsFileTransferCreationParameters implements Parcelable {
+ private String mRcsFileTransferSessionId;
+ private Uri mContentUri;
+ private String mContentMimeType;
+ private long mFileSize;
+ private long mTransferOffset;
+ private int mWidth;
+ private int mHeight;
+ private long mMediaDuration;
+ private Uri mPreviewUri;
+ private String mPreviewMimeType;
+ private @RcsFileTransferPart.RcsFileTransferStatus int mFileTransferStatus;
+
+ /**
+ * @return Returns the globally unique RCS file transfer session ID for the
+ * {@link RcsFileTransferPart} to be created
+ */
+ public String getRcsFileTransferSessionId() {
+ return mRcsFileTransferSessionId;
+ }
+
+ /**
+ * @return Returns the URI for the content of the {@link RcsFileTransferPart} to be created
+ */
+ public Uri getContentUri() {
+ return mContentUri;
+ }
+
+ /**
+ * @return Returns the MIME type for the content of the {@link RcsFileTransferPart} to be
+ * created
+ */
+ public String getContentMimeType() {
+ return mContentMimeType;
+ }
+
+ /**
+ * @return Returns the file size in bytes for the {@link RcsFileTransferPart} to be created
+ */
+ public long getFileSize() {
+ return mFileSize;
+ }
+
+ /**
+ * @return Returns the transfer offset for the {@link RcsFileTransferPart} to be created. The
+ * file transfer offset is defined as how many bytes have been successfully transferred to the
+ * receiver of this file transfer.
+ */
+ public long getTransferOffset() {
+ return mTransferOffset;
+ }
+
+ /**
+ * @return Returns the width of the {@link RcsFileTransferPart} to be created. The value is in
+ * pixels.
+ */
+ public int getWidth() {
+ return mWidth;
+ }
+
+ /**
+ * @return Returns the height of the {@link RcsFileTransferPart} to be created. The value is in
+ * pixels.
+ */
+ public int getHeight() {
+ return mHeight;
+ }
+
+ /**
+ * @return Returns the duration of the {@link RcsFileTransferPart} to be created.
+ */
+ public long getMediaDuration() {
+ return mMediaDuration;
+ }
+
+ /**
+ * @return Returns the URI of the preview of the content of the {@link RcsFileTransferPart} to
+ * be created. This should only be used for multi-media files.
+ */
+ public Uri getPreviewUri() {
+ return mPreviewUri;
+ }
+
+ /**
+ * @return Returns the MIME type of the preview of the content of the
+ * {@link RcsFileTransferPart} to be created. This should only be used for multi-media files.
+ */
+ public String getPreviewMimeType() {
+ return mPreviewMimeType;
+ }
+
+ /**
+ * @return Returns the status of the {@link RcsFileTransferPart} to be created.
+ */
+ public @RcsFileTransferPart.RcsFileTransferStatus int getFileTransferStatus() {
+ return mFileTransferStatus;
+ }
+
+ /**
+ * @hide
+ */
+ RcsFileTransferCreationParameters(Builder builder) {
+ mRcsFileTransferSessionId = builder.mRcsFileTransferSessionId;
+ mContentUri = builder.mContentUri;
+ mContentMimeType = builder.mContentMimeType;
+ mFileSize = builder.mFileSize;
+ mTransferOffset = builder.mTransferOffset;
+ mWidth = builder.mWidth;
+ mHeight = builder.mHeight;
+ mMediaDuration = builder.mLength;
+ mPreviewUri = builder.mPreviewUri;
+ mPreviewMimeType = builder.mPreviewMimeType;
+ mFileTransferStatus = builder.mFileTransferStatus;
+ }
+
+ /**
+ * A builder to create instances of {@link RcsFileTransferCreationParameters}
+ */
+ public class Builder {
+ private String mRcsFileTransferSessionId;
+ private Uri mContentUri;
+ private String mContentMimeType;
+ private long mFileSize;
+ private long mTransferOffset;
+ private int mWidth;
+ private int mHeight;
+ private long mLength;
+ private Uri mPreviewUri;
+ private String mPreviewMimeType;
+ private @RcsFileTransferPart.RcsFileTransferStatus int mFileTransferStatus;
+
+ /**
+ * Sets the globally unique RCS file transfer session ID for the {@link RcsFileTransferPart}
+ * to be created
+ *
+ * @param sessionId The RCS file transfer session ID
+ * @return The same instance of {@link Builder} to chain methods
+ */
+ @CheckResult
+ public Builder setFileTransferSessionId(String sessionId) {
+ mRcsFileTransferSessionId = sessionId;
+ return this;
+ }
+
+ /**
+ * Sets the URI for the content of the {@link RcsFileTransferPart} to be created
+ *
+ * @param contentUri The URI for the file
+ * @return The same instance of {@link Builder} to chain methods
+ */
+ @CheckResult
+ public Builder setContentUri(Uri contentUri) {
+ mContentUri = contentUri;
+ return this;
+ }
+
+ /**
+ * Sets the MIME type for the content of the {@link RcsFileTransferPart} to be created
+ *
+ * @param contentType The MIME type of the file
+ * @return The same instance of {@link Builder} to chain methods
+ */
+ @CheckResult
+ public Builder setContentMimeType(String contentType) {
+ mContentMimeType = contentType;
+ return this;
+ }
+
+ /**
+ * Sets the file size for the {@link RcsFileTransferPart} to be created
+ *
+ * @param size The size of the file in bytes
+ * @return The same instance of {@link Builder} to chain methods
+ */
+ @CheckResult
+ public Builder setFileSize(long size) {
+ mFileSize = size;
+ return this;
+ }
+
+ /**
+ * Sets the transfer offset for the {@link RcsFileTransferPart} to be created. The file
+ * transfer offset is defined as how many bytes have been successfully transferred to the
+ * receiver of this file transfer.
+ *
+ * @param offset The transfer offset in bytes
+ * @return The same instance of {@link Builder} to chain methods
+ */
+ @CheckResult
+ public Builder setTransferOffset(long offset) {
+ mTransferOffset = offset;
+ return this;
+ }
+
+ /**
+ * Sets the width of the {@link RcsFileTransferPart} to be created. This should only be used
+ * for multi-media files.
+ *
+ * @param width The width of the multi-media file in pixels.
+ * @return The same instance of {@link Builder} to chain methods
+ */
+ @CheckResult
+ public Builder setWidth(int width) {
+ mWidth = width;
+ return this;
+ }
+
+ /**
+ * Sets the height of the {@link RcsFileTransferPart} to be created. This should only be
+ * used for multi-media files.
+ *
+ * @param height The height of the multi-media file in pixels.
+ * @return The same instance of {@link Builder} to chain methods
+ */
+ @CheckResult
+ public Builder setHeight(int height) {
+ mHeight = height;
+ return this;
+ }
+
+ /**
+ * Sets the length of the {@link RcsFileTransferPart} to be created. This should only be
+ * used for multi-media files such as audio or video.
+ *
+ * @param length The length of the multi-media file in milliseconds
+ * @return The same instance of {@link Builder} to chain methods
+ */
+ @CheckResult
+ public Builder setMediaDuration(long length) {
+ mLength = length;
+ return this;
+ }
+
+ /**
+ * Sets the URI of the preview of the content of the {@link RcsFileTransferPart} to be
+ * created. This should only be used for multi-media files.
+ *
+ * @param previewUri The URI of the preview of the file transfer
+ * @return The same instance of {@link Builder} to chain methods
+ */
+ @CheckResult
+ public Builder setPreviewUri(Uri previewUri) {
+ mPreviewUri = previewUri;
+ return this;
+ }
+
+ /**
+ * Sets the MIME type of the preview of the content of the {@link RcsFileTransferPart} to
+ * be created. This should only be used for multi-media files.
+ *
+ * @param previewType The MIME type of the preview of the file transfer
+ * @return The same instance of {@link Builder} to chain methods
+ */
+ @CheckResult
+ public Builder setPreviewMimeType(String previewType) {
+ mPreviewMimeType = previewType;
+ return this;
+ }
+
+ /**
+ * Sets the status of the {@link RcsFileTransferPart} to be created.
+ *
+ * @param status The status of the file transfer
+ * @return The same instance of {@link Builder} to chain methods
+ */
+ @CheckResult
+ public Builder setFileTransferStatus(
+ @RcsFileTransferPart.RcsFileTransferStatus int status) {
+ mFileTransferStatus = status;
+ return this;
+ }
+
+ /**
+ * Creates an instance of {@link RcsFileTransferCreationParameters} with the given
+ * parameters.
+ *
+ * @return The same instance of {@link Builder} to chain methods
+ * @see RcsMessage#insertFileTransfer(RcsFileTransferCreationParameters)
+ */
+ public RcsFileTransferCreationParameters build() {
+ return new RcsFileTransferCreationParameters(this);
+ }
+ }
+
+ protected RcsFileTransferCreationParameters(Parcel in) {
+ mRcsFileTransferSessionId = in.readString();
+ mContentUri = in.readParcelable(Uri.class.getClassLoader());
+ mContentMimeType = in.readString();
+ mFileSize = in.readLong();
+ mTransferOffset = in.readLong();
+ mWidth = in.readInt();
+ mHeight = in.readInt();
+ mMediaDuration = in.readLong();
+ mPreviewUri = in.readParcelable(Uri.class.getClassLoader());
+ mPreviewMimeType = in.readString();
+ mFileTransferStatus = in.readInt();
+ }
+
+ public static final Creator<RcsFileTransferCreationParameters> CREATOR =
+ new Creator<RcsFileTransferCreationParameters>() {
+ @Override
+ public RcsFileTransferCreationParameters createFromParcel(Parcel in) {
+ return new RcsFileTransferCreationParameters(in);
+ }
+
+ @Override
+ public RcsFileTransferCreationParameters[] newArray(int size) {
+ return new RcsFileTransferCreationParameters[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mRcsFileTransferSessionId);
+ dest.writeParcelable(mContentUri, flags);
+ dest.writeString(mContentMimeType);
+ dest.writeLong(mFileSize);
+ dest.writeLong(mTransferOffset);
+ dest.writeInt(mWidth);
+ dest.writeInt(mHeight);
+ dest.writeLong(mMediaDuration);
+ dest.writeParcelable(mPreviewUri, flags);
+ dest.writeString(mPreviewMimeType);
+ dest.writeInt(mFileTransferStatus);
+ }
+}
diff --git a/telephony/java/android/telephony/ims/RcsFileTransferPart.aidl b/telephony/java/android/telephony/ims/RcsFileTransferPart.aidl
deleted file mode 100644
index eaf3128..0000000
--- a/telephony/java/android/telephony/ims/RcsFileTransferPart.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- *
- * Copyright 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.telephony.ims;
-
-parcelable RcsFileTransferPart;
diff --git a/telephony/java/android/telephony/ims/RcsFileTransferPart.java b/telephony/java/android/telephony/ims/RcsFileTransferPart.java
index 39c58dd..2eadc4a 100644
--- a/telephony/java/android/telephony/ims/RcsFileTransferPart.java
+++ b/telephony/java/android/telephony/ims/RcsFileTransferPart.java
@@ -15,34 +15,346 @@
*/
package android.telephony.ims;
-import android.os.Parcel;
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.WorkerThread;
+import android.net.Uri;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
/**
- * A part of a composite {@link RcsMessage} that holds a file transfer.
- * @hide - TODO(sahinc) make this public
+ * A part of a composite {@link RcsMessage} that holds a file transfer. Please see Section 7
+ * (File Transfer) - GSMA RCC.71 (RCS Universal Profile Service Definition Document)
+ *
+ * @hide - TODO(109759350) make this public
*/
-public class RcsFileTransferPart extends RcsPart {
- public static final Creator<RcsFileTransferPart> CREATOR = new Creator<RcsFileTransferPart>() {
- @Override
- public RcsFileTransferPart createFromParcel(Parcel in) {
- return new RcsFileTransferPart(in);
- }
+public class RcsFileTransferPart {
+ /**
+ * The status to indicate that this {@link RcsFileTransferPart} is not set yet.
+ */
+ public static final int NOT_SET = 0;
- @Override
- public RcsFileTransferPart[] newArray(int size) {
- return new RcsFileTransferPart[size];
- }
- };
+ /**
+ * The status to indicate that this {@link RcsFileTransferPart} is a draft and is not in the
+ * process of sending yet.
+ */
+ public static final int DRAFT = 1;
- protected RcsFileTransferPart(Parcel in) {
+ /**
+ * The status to indicate that this {@link RcsFileTransferPart} is actively being sent right
+ * now.
+ */
+ public static final int SENDING = 2;
+
+ /**
+ * The status to indicate that this {@link RcsFileTransferPart} was being sent, but the user has
+ * paused the sending process.
+ */
+ public static final int SENDING_PAUSED = 3;
+
+ /**
+ * The status to indicate that this {@link RcsFileTransferPart} was attempted, but failed to
+ * send.
+ */
+ public static final int SENDING_FAILED = 4;
+
+ /**
+ * The status to indicate that this {@link RcsFileTransferPart} is permanently cancelled to
+ * send.
+ */
+ public static final int SENDING_CANCELLED = 5;
+
+ /**
+ * The status to indicate that this {@link RcsFileTransferPart} is actively being downloaded
+ * right now.
+ */
+ public static final int DOWNLOADING = 6;
+
+ /**
+ * The status to indicate that this {@link RcsFileTransferPart} was being downloaded, but the
+ * user paused the downloading process.
+ */
+ public static final int DOWNLOADING_PAUSED = 7;
+
+ /**
+ * The status to indicate that this {@link RcsFileTransferPart} was attempted, but failed to
+ * download.
+ */
+ public static final int DOWNLOADING_FAILED = 8;
+
+ /**
+ * The status to indicate that this {@link RcsFileTransferPart} is permanently cancelled to
+ * download.
+ */
+ public static final int DOWNLOADING_CANCELLED = 9;
+
+ /**
+ * The status to indicate that this {@link RcsFileTransferPart} was successfully sent or
+ * received.
+ */
+ public static final int SUCCEEDED = 10;
+
+ @IntDef({
+ DRAFT, SENDING, SENDING_PAUSED, SENDING_FAILED, SENDING_CANCELLED, DOWNLOADING,
+ DOWNLOADING_PAUSED, DOWNLOADING_FAILED, DOWNLOADING_CANCELLED, SUCCEEDED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RcsFileTransferStatus {
}
- @Override
- public int describeContents() {
- return 0;
+ private int mId;
+
+ /**
+ * @hide
+ */
+ RcsFileTransferPart(int id) {
+ mId = id;
}
- @Override
- public void writeToParcel(Parcel dest, int flags) {
+ /**
+ * @hide
+ */
+ public void setId(int id) {
+ mId = id;
+ }
+
+ /**
+ * @hide
+ */
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * Sets the RCS file transfer session ID for this file transfer and persists into storage.
+ *
+ * @param sessionId The session ID to be used for this file transfer.
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setFileTransferSessionId(String sessionId) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferSessionId(mId, sessionId));
+ }
+
+ /**
+ * @return Returns the file transfer session ID.
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @WorkerThread
+ public String getFileTransferSessionId() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getFileTransferSessionId(mId));
+ }
+
+ /**
+ * Sets the content URI for this file transfer and persists into storage. The file transfer
+ * should be reachable using this URI.
+ *
+ * @param contentUri The URI for this file transfer.
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setContentUri(Uri contentUri) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferContentUri(mId, contentUri));
+ }
+
+ /**
+ * @return Returns the URI for this file transfer
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @Nullable
+ @WorkerThread
+ public Uri getContentUri() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getFileTransferContentUri(mId));
+ }
+
+ /**
+ * Sets the MIME type of this file transfer and persists into storage. Whether this type
+ * actually matches any known or supported types is not checked.
+ *
+ * @param contentMimeType The type of this file transfer.
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setContentMimeType(String contentMimeType) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(
+ iRcs -> iRcs.setFileTransferContentType(mId, contentMimeType));
+ }
+
+ /**
+ * @return Returns the content type of this file transfer
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @WorkerThread
+ @Nullable
+ public String getContentMimeType() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getFileTransferContentType(mId));
+ }
+
+ /**
+ * Sets the content length (i.e. file size) for this file transfer and persists into storage.
+ *
+ * @param contentLength The content length of this file transfer
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setFileSize(long contentLength) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(
+ iRcs -> iRcs.setFileTransferFileSize(mId, contentLength));
+ }
+
+ /**
+ * @return Returns the content length (i.e. file size) for this file transfer.
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @WorkerThread
+ public long getFileSize() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getFileTransferFileSize(mId));
+ }
+
+ /**
+ * Sets the transfer offset for this file transfer and persists into storage. The file transfer
+ * offset is defined as how many bytes have been successfully transferred to the receiver of
+ * this file transfer.
+ *
+ * @param transferOffset The transfer offset for this file transfer.
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setTransferOffset(long transferOffset) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(
+ iRcs -> iRcs.setFileTransferTransferOffset(mId, transferOffset));
+ }
+
+ /**
+ * @return Returns the number of bytes that have successfully transferred.
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @WorkerThread
+ public long getTransferOffset() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getFileTransferTransferOffset(mId));
+ }
+
+ /**
+ * Sets the status for this file transfer and persists into storage.
+ *
+ * @param status The status of this file transfer.
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setFileTransferStatus(@RcsFileTransferStatus int status)
+ throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferStatus(mId, status));
+ }
+
+ /**
+ * @return Returns the status of this file transfer.
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @WorkerThread
+ public @RcsFileTransferStatus int getFileTransferStatus() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getFileTransferStatus(mId));
+ }
+
+ /**
+ * @return Returns the width of this multi-media message part in pixels.
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @WorkerThread
+ public int getWidth() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getFileTransferWidth(mId));
+ }
+
+ /**
+ * Sets the width of this RCS multi-media message part and persists into storage.
+ *
+ * @param width The width value in pixels
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setWidth(int width) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferWidth(mId, width));
+ }
+
+ /**
+ * @return Returns the height of this multi-media message part in pixels.
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @WorkerThread
+ public int getHeight() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getFileTransferHeight(mId));
+ }
+
+ /**
+ * Sets the height of this RCS multi-media message part and persists into storage.
+ *
+ * @param height The height value in pixels
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setHeight(int height) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferHeight(mId, height));
+ }
+
+ /**
+ * @return Returns the length of this multi-media file (e.g. video or audio) in milliseconds.
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @WorkerThread
+ public long getLength() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getFileTransferLength(mId));
+ }
+
+ /**
+ * Sets the length of this multi-media file (e.g. video or audio) and persists into storage.
+ *
+ * @param length The length of the file in milliseconds.
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setLength(long length) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferLength(mId, length));
+ }
+
+ /**
+ * @return Returns the URI for the preview of this multi-media file (e.g. an image thumbnail for
+ * a video)
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @WorkerThread
+ public Uri getPreviewUri() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getFileTransferPreviewUri(mId));
+ }
+
+ /**
+ * Sets the URI for the preview of this multi-media file and persists into storage.
+ *
+ * @param previewUri The URI to access to the preview file.
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setPreviewUri(Uri previewUri) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setFileTransferPreviewUri(mId, previewUri));
+ }
+
+ /**
+ * @return Returns the MIME type of this multi-media file's preview.
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @WorkerThread
+ public String getPreviewMimeType() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getFileTransferPreviewType(mId));
+ }
+
+ /**
+ * Sets the MIME type for this multi-media file's preview and persists into storage.
+ *
+ * @param previewMimeType The MIME type for the preview
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setPreviewMimeType(String previewMimeType) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(
+ iRcs -> iRcs.setFileTransferPreviewType(mId, previewMimeType));
}
}
diff --git a/telephony/java/android/telephony/ims/RcsGroupThread.aidl b/telephony/java/android/telephony/ims/RcsGroupThread.aidl
deleted file mode 100644
index c4ce529..0000000
--- a/telephony/java/android/telephony/ims/RcsGroupThread.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- *
- * Copyright 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.telephony.ims;
-
-parcelable RcsGroupThread;
diff --git a/telephony/java/android/telephony/ims/RcsGroupThread.java b/telephony/java/android/telephony/ims/RcsGroupThread.java
index d954b2d..6f6258e 100644
--- a/telephony/java/android/telephony/ims/RcsGroupThread.java
+++ b/telephony/java/android/telephony/ims/RcsGroupThread.java
@@ -15,38 +15,191 @@
*/
package android.telephony.ims;
-import android.os.Parcel;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.WorkerThread;
+import android.net.Uri;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
/**
* RcsGroupThread represents a single RCS conversation thread where {@link RcsParticipant}s can join
- * or leave.
+ * or leave. Please see Section 6 (Group Chat) - GSMA RCC.71 (RCS Universal Profile Service
+ * Definition Document)
+ *
* @hide - TODO(sahinc) make this public
*/
public class RcsGroupThread extends RcsThread {
- public static final Creator<RcsGroupThread> CREATOR = new Creator<RcsGroupThread>() {
- @Override
- public RcsGroupThread createFromParcel(Parcel in) {
- return new RcsGroupThread(in);
- }
-
- @Override
- public RcsGroupThread[] newArray(int size) {
- return new RcsGroupThread[size];
- }
- };
-
- protected RcsGroupThread(Parcel in) {
- super(in);
+ /**
+ * Public constructor only for RcsMessageStoreController to initialize new threads.
+ *
+ * @hide
+ */
+ public RcsGroupThread(int threadId) {
+ super(threadId);
}
+ /**
+ * @return Returns {@code true} as this is always a group thread
+ */
@Override
- public int describeContents() {
- return 0;
+ public boolean isGroup() {
+ return true;
}
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(RCS_GROUP_TYPE);
- super.writeToParcel(dest, flags);
+ /**
+ * @return Returns the given name of this {@link RcsGroupThread}. Please see US6-2 - GSMA RCC.71
+ * (RCS Universal Profile Service Definition Document)
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @Nullable
+ @WorkerThread
+ public String getGroupName() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getGroupThreadName(mThreadId));
+ }
+
+ /**
+ * Sets the name of this {@link RcsGroupThread} and saves it into storage. Please see US6-2 -
+ * GSMA RCC.71 (RCS Universal Profile Service Definition Document)
+ *
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setGroupName(String groupName) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setGroupThreadName(mThreadId, groupName));
+ }
+
+ /**
+ * @return Returns a URI that points to the group's icon {@link RcsGroupThread}. Please see
+ * US6-2 - GSMA RCC.71 (RCS Universal Profile Service Definition Document)
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @Nullable
+ public Uri getGroupIcon() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getGroupThreadIcon(mThreadId));
+ }
+
+ /**
+ * Sets the icon for this {@link RcsGroupThread} and saves it into storage. Please see US6-2 -
+ * GSMA RCC.71 (RCS Universal Profile Service Definition Document)
+ *
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setGroupIcon(@Nullable Uri groupIcon) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setGroupThreadIcon(mThreadId, groupIcon));
+ }
+
+ /**
+ * @return Returns the owner of this thread or {@code null} if there doesn't exist an owner
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @Nullable
+ @WorkerThread
+ public RcsParticipant getOwner() throws RcsMessageStoreException {
+ return new RcsParticipant(RcsControllerCall.call(
+ iRcs -> iRcs.getGroupThreadOwner(mThreadId)));
+ }
+
+ /**
+ * Sets the owner of this {@link RcsGroupThread} and saves it into storage. This is intended to
+ * be used for selecting a new owner for a group thread if the owner leaves the thread. The
+ * owner needs to be in the list of existing participants.
+ *
+ * @param participant The new owner of the thread. {@code null} values are allowed.
+ * @throws RcsMessageStoreException if the operation could not be persisted into storage
+ */
+ @WorkerThread
+ public void setOwner(@Nullable RcsParticipant participant) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(
+ iRcs -> iRcs.setGroupThreadOwner(mThreadId, participant.getId()));
+ }
+
+ /**
+ * Adds a new {@link RcsParticipant} to this group thread and persists into storage. If the user
+ * is actively participating in this {@link RcsGroupThread}, an {@link RcsParticipant} on behalf
+ * of them should be added.
+ *
+ * @param participant The new participant to be added to the thread.
+ * @throws RcsMessageStoreException if the operation could not be persisted into storage
+ */
+ @WorkerThread
+ public void addParticipant(@NonNull RcsParticipant participant)
+ throws RcsMessageStoreException {
+ if (participant == null) {
+ return;
+ }
+
+ RcsControllerCall.callWithNoReturn(
+ iRcs -> iRcs.addParticipantToGroupThread(mThreadId, participant.getId()));
+ }
+
+ /**
+ * Removes an {@link RcsParticipant} from this group thread and persists into storage. If the
+ * removed participant was the owner of this group, the owner will become null.
+ *
+ * @throws RcsMessageStoreException if the operation could not be persisted into storage
+ */
+ @WorkerThread
+ public void removeParticipant(@NonNull RcsParticipant participant)
+ throws RcsMessageStoreException {
+ if (participant == null) {
+ return;
+ }
+
+ RcsControllerCall.callWithNoReturn(
+ iRcs -> iRcs.removeParticipantFromGroupThread(mThreadId, participant.getId()));
+ }
+
+ /**
+ * Returns the set of {@link RcsParticipant}s that contribute to this group thread. The
+ * returned set does not support modifications, please use
+ * {@link RcsGroupThread#addParticipant(RcsParticipant)}
+ * and {@link RcsGroupThread#removeParticipant(RcsParticipant)} instead.
+ *
+ * @return the immutable set of {@link RcsParticipant} in this group thread.
+ * @throws RcsMessageStoreException if the values could not be read from the storage
+ */
+ @WorkerThread
+ @NonNull
+ public Set<RcsParticipant> getParticipants() throws RcsMessageStoreException {
+ RcsParticipantQueryParameters queryParameters =
+ new RcsParticipantQueryParameters.Builder().setThread(this).build();
+
+ RcsParticipantQueryResult queryResult = RcsControllerCall.call(
+ iRcs -> iRcs.getParticipants(queryParameters));
+
+ List<RcsParticipant> participantList = queryResult.getParticipants();
+ Set<RcsParticipant> participantSet = new LinkedHashSet<>(participantList);
+ return Collections.unmodifiableSet(participantSet);
+ }
+
+ /**
+ * Returns the conference URI for this {@link RcsGroupThread}. Please see 4.4.5.2 - GSMA RCC.53
+ * (RCS Device API 1.6 Specification
+ *
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @Nullable
+ @WorkerThread
+ public Uri getConferenceUri() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getGroupThreadConferenceUri(mThreadId));
+ }
+
+ /**
+ * Sets the conference URI for this {@link RcsGroupThread} and persists into storage. Please see
+ * 4.4.5.2 - GSMA RCC.53 (RCS Device API 1.6 Specification
+ *
+ * @param conferenceUri The URI as String to be used as the conference URI.
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @Nullable
+ @WorkerThread
+ public void setConferenceUri(Uri conferenceUri) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(
+ iRcs -> iRcs.setGroupThreadConferenceUri(mThreadId, conferenceUri));
}
}
diff --git a/telephony/java/android/telephony/ims/RcsThreadEvent.aidl b/telephony/java/android/telephony/ims/RcsGroupThreadEvent.aidl
similarity index 95%
rename from telephony/java/android/telephony/ims/RcsThreadEvent.aidl
rename to telephony/java/android/telephony/ims/RcsGroupThreadEvent.aidl
index 4a40d89..77a2372 100644
--- a/telephony/java/android/telephony/ims/RcsThreadEvent.aidl
+++ b/telephony/java/android/telephony/ims/RcsGroupThreadEvent.aidl
@@ -1,5 +1,4 @@
/*
- *
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,4 +16,4 @@
package android.telephony.ims;
-parcelable RcsThreadEvent;
+parcelable RcsGroupThreadEvent;
diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadEvent.java b/telephony/java/android/telephony/ims/RcsGroupThreadEvent.java
new file mode 100644
index 0000000..a18437b
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsGroupThreadEvent.java
@@ -0,0 +1,65 @@
+/*
+ * 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.telephony.ims;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+
+/**
+ * An event that happened on an {@link RcsGroupThread}.
+ *
+ * @hide - TODO(109759350) make this public
+ */
+public abstract class RcsGroupThreadEvent extends RcsEvent {
+ private final int mRcsGroupThreadId;
+ private final int mOriginatingParticipantId;
+
+ RcsGroupThreadEvent(long timestamp, int rcsGroupThreadId,
+ int originatingParticipantId) {
+ super(timestamp);
+ mRcsGroupThreadId = rcsGroupThreadId;
+ mOriginatingParticipantId = originatingParticipantId;
+ }
+
+ /**
+ * @return Returns the {@link RcsGroupThread} that this event happened on.
+ */
+ @NonNull
+ public RcsGroupThread getRcsGroupThread() {
+ return new RcsGroupThread(mRcsGroupThreadId);
+ }
+
+ /**
+ * @return Returns the {@link RcsParticipant} that performed the event.
+ */
+ @NonNull
+ public RcsParticipant getOriginatingParticipant() {
+ return new RcsParticipant(mOriginatingParticipantId);
+ }
+
+ RcsGroupThreadEvent(Parcel in) {
+ super(in);
+ mRcsGroupThreadId = in.readInt();
+ mOriginatingParticipantId = in.readInt();
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mRcsGroupThreadId);
+ dest.writeInt(mOriginatingParticipantId);
+ }
+}
diff --git a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl b/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEvent.aidl
similarity index 93%
copy from telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
copy to telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEvent.aidl
index 82d985d..daea792 100644
--- a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
+++ b/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEvent.aidl
@@ -1,5 +1,4 @@
/*
- *
* Copyright 2019, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
@@ -17,4 +16,4 @@
package android.telephony.ims;
-parcelable RcsThreadIconChangedEvent;
+parcelable RcsGroupThreadIconChangedEvent;
diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEvent.java b/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEvent.java
new file mode 100644
index 0000000..7beed3b
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsGroupThreadIconChangedEvent.java
@@ -0,0 +1,109 @@
+/*
+ * 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.telephony.ims;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.net.Uri;
+import android.os.Parcel;
+
+/**
+ * An event that indicates an {@link RcsGroupThread}'s icon was changed. Please see R6-2-5 - GSMA
+ * RCC.71 (RCS Universal Profile Service Definition Document)
+ *
+ * @hide - TODO(109759350) make this public
+ */
+public class RcsGroupThreadIconChangedEvent extends RcsGroupThreadEvent {
+ private final Uri mNewIcon;
+
+ /**
+ * Creates a new {@link RcsGroupThreadIconChangedEvent}. This event is not persisted into
+ * storage until {@link RcsMessageStore#persistRcsEvent(RcsEvent)} is called.
+ *
+ * @param timestamp The timestamp of when this event happened, in milliseconds passed after
+ * midnight, January 1st, 1970 UTC
+ * @param rcsGroupThread The {@link RcsGroupThread} that this event happened on
+ * @param originatingParticipant The {@link RcsParticipant} that changed the
+ * {@link RcsGroupThread}'s icon.
+ * @param newIcon {@link Uri} to the new icon of this {@link RcsGroupThread}
+ * @see RcsMessageStore#persistRcsEvent(RcsEvent)
+ */
+ public RcsGroupThreadIconChangedEvent(long timestamp, @NonNull RcsGroupThread rcsGroupThread,
+ @NonNull RcsParticipant originatingParticipant, @Nullable Uri newIcon) {
+ super(timestamp, rcsGroupThread.getThreadId(), originatingParticipant.getId());
+ mNewIcon = newIcon;
+ }
+
+ /**
+ * @hide - internal constructor for queries
+ */
+ public RcsGroupThreadIconChangedEvent(long timestamp, int rcsGroupThreadId,
+ int originatingParticipantId, @Nullable Uri newIcon) {
+ super(timestamp, rcsGroupThreadId, originatingParticipantId);
+ mNewIcon = newIcon;
+ }
+
+ /**
+ * @return Returns the {@link Uri} to the icon of the {@link RcsGroupThread} after this
+ * {@link RcsGroupThreadIconChangedEvent} occured.
+ */
+ @Nullable
+ public Uri getNewIcon() {
+ return mNewIcon;
+ }
+
+ /**
+ * Persists the event to the data store.
+ *
+ * @hide - not meant for public use.
+ */
+ @Override
+ public void persist() throws RcsMessageStoreException {
+ // TODO ensure failure throws
+ RcsControllerCall.call(iRcs -> iRcs.createGroupThreadIconChangedEvent(
+ getTimestamp(), getRcsGroupThread().getThreadId(),
+ getOriginatingParticipant().getId(), mNewIcon));
+ }
+
+ public static final Creator<RcsGroupThreadIconChangedEvent> CREATOR =
+ new Creator<RcsGroupThreadIconChangedEvent>() {
+ @Override
+ public RcsGroupThreadIconChangedEvent createFromParcel(Parcel in) {
+ return new RcsGroupThreadIconChangedEvent(in);
+ }
+
+ @Override
+ public RcsGroupThreadIconChangedEvent[] newArray(int size) {
+ return new RcsGroupThreadIconChangedEvent[size];
+ }
+ };
+
+ protected RcsGroupThreadIconChangedEvent(Parcel in) {
+ super(in);
+ mNewIcon = in.readParcelable(Uri.class.getClassLoader());
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeParcelable(mNewIcon, flags);
+ }
+}
diff --git a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl b/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEvent.aidl
similarity index 93%
copy from telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
copy to telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEvent.aidl
index 82d985d..3ed9bd1 100644
--- a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
+++ b/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEvent.aidl
@@ -17,4 +17,4 @@
package android.telephony.ims;
-parcelable RcsThreadIconChangedEvent;
+parcelable RcsGroupThreadNameChangedEvent;
diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEvent.java b/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEvent.java
new file mode 100644
index 0000000..0d2ea4f
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsGroupThreadNameChangedEvent.java
@@ -0,0 +1,107 @@
+/*
+ * 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.telephony.ims;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+
+/**
+ * An event that indicates an {@link RcsGroupThread}'s name was changed. Please see R6-2-5 - GSMA
+ * RCC.71 (RCS Universal Profile Service Definition Document)
+ *
+ * @hide - TODO(109759350) make this public
+ */
+public class RcsGroupThreadNameChangedEvent extends RcsGroupThreadEvent {
+ private String mNewName;
+
+ /**
+ * Creates a new {@link RcsGroupThreadNameChangedEvent}. This event is not persisted into
+ * storage until {@link RcsMessageStore#persistRcsEvent(RcsEvent)} is called.
+ *
+ * @param timestamp The timestamp of when this event happened, in milliseconds passed after
+ * midnight, January 1st, 1970 UTC
+ * @param rcsGroupThread The {@link RcsGroupThread} that this event happened on
+ * @param originatingParticipant The {@link RcsParticipant} that changed the
+ * {@link RcsGroupThread}'s icon.
+ * @param newName The new name of the {@link RcsGroupThread}
+ * @see RcsMessageStore#persistRcsEvent(RcsEvent)
+ */
+ public RcsGroupThreadNameChangedEvent(long timestamp, @NonNull RcsGroupThread rcsGroupThread,
+ @NonNull RcsParticipant originatingParticipant, @Nullable String newName) {
+ super(timestamp, rcsGroupThread.getThreadId(), originatingParticipant.getId());
+ mNewName = newName;
+ }
+
+ /**
+ * @hide - internal constructor for queries
+ */
+ public RcsGroupThreadNameChangedEvent(long timestamp, int rcsGroupThreadId,
+ int originatingParticipantId, @Nullable String newName) {
+ super(timestamp, rcsGroupThreadId, originatingParticipantId);
+ mNewName = newName;
+ }
+
+ /**
+ * @return Returns the name of this {@link RcsGroupThread} after this
+ * {@link RcsGroupThreadNameChangedEvent} happened.
+ */
+ @Nullable
+ public String getNewName() {
+ return mNewName;
+ }
+
+ /**
+ * Persists the event to the data store.
+ *
+ * @hide - not meant for public use.
+ */
+ @Override
+ public void persist() throws RcsMessageStoreException {
+ RcsControllerCall.call(iRcs -> iRcs.createGroupThreadNameChangedEvent(
+ getTimestamp(), getRcsGroupThread().getThreadId(),
+ getOriginatingParticipant().getId(), mNewName));
+ }
+
+ public static final Creator<RcsGroupThreadNameChangedEvent> CREATOR =
+ new Creator<RcsGroupThreadNameChangedEvent>() {
+ @Override
+ public RcsGroupThreadNameChangedEvent createFromParcel(Parcel in) {
+ return new RcsGroupThreadNameChangedEvent(in);
+ }
+
+ @Override
+ public RcsGroupThreadNameChangedEvent[] newArray(int size) {
+ return new RcsGroupThreadNameChangedEvent[size];
+ }
+ };
+
+ protected RcsGroupThreadNameChangedEvent(Parcel in) {
+ super(in);
+ mNewName = in.readString();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(mNewName);
+ }
+}
diff --git a/telephony/java/android/telephony/ims/RcsMessage.aidl b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEvent.aidl
similarity index 92%
copy from telephony/java/android/telephony/ims/RcsMessage.aidl
copy to telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEvent.aidl
index b32cd12..420abff 100644
--- a/telephony/java/android/telephony/ims/RcsMessage.aidl
+++ b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEvent.aidl
@@ -17,4 +17,4 @@
package android.telephony.ims;
-parcelable RcsMessage;
+parcelable RcsGroupThreadParticipantJoinedEvent;
diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEvent.java b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEvent.java
new file mode 100644
index 0000000..2adafc7
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantJoinedEvent.java
@@ -0,0 +1,107 @@
+/*
+ * 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.telephony.ims;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+
+/**
+ * An event that indicates an RCS participant has joined an {@link RcsThread}. Please see US6-3 -
+ * GSMA RCC.71 (RCS Universal Profile Service Definition Document)
+ *
+ * @hide - TODO(109759350) make this public
+ */
+public class RcsGroupThreadParticipantJoinedEvent extends RcsGroupThreadEvent {
+ private int mJoinedParticipantId;
+
+ /**
+ * Creates a new {@link RcsGroupThreadParticipantJoinedEvent}. This event is not persisted into
+ * storage until {@link RcsMessageStore#persistRcsEvent(RcsEvent)} is called.
+ *
+ * @param timestamp The timestamp of when this event happened, in milliseconds passed after
+ * midnight, January 1st, 1970 UTC
+ * @param rcsGroupThread The {@link RcsGroupThread} that this event happened on
+ * @param originatingParticipant The {@link RcsParticipant} that added or invited the new
+ * {@link RcsParticipant} into the {@link RcsGroupThread}
+ * @param joinedParticipant The new {@link RcsParticipant} that joined the
+ * {@link RcsGroupThread}
+ * @see RcsMessageStore#persistRcsEvent(RcsEvent)
+ */
+ public RcsGroupThreadParticipantJoinedEvent(long timestamp,
+ @NonNull RcsGroupThread rcsGroupThread, @NonNull RcsParticipant originatingParticipant,
+ @NonNull RcsParticipant joinedParticipant) {
+ super(timestamp, rcsGroupThread.getThreadId(), originatingParticipant.getId());
+ mJoinedParticipantId = joinedParticipant.getId();
+ }
+
+ /**
+ * @hide - internal constructor for queries
+ */
+ public RcsGroupThreadParticipantJoinedEvent(long timestamp, int rcsGroupThreadId,
+ int originatingParticipantId, int joinedParticipantId) {
+ super(timestamp, rcsGroupThreadId, originatingParticipantId);
+ mJoinedParticipantId = joinedParticipantId;
+ }
+
+ /**
+ * @return Returns the {@link RcsParticipant} that joined the associated {@link RcsGroupThread}
+ */
+ public RcsParticipant getJoinedParticipant() {
+ return new RcsParticipant(mJoinedParticipantId);
+ }
+
+ /**
+ * Persists the event to the data store.
+ *
+ * @hide - not meant for public use.
+ */
+ @Override
+ public void persist() throws RcsMessageStoreException {
+ RcsControllerCall.call(
+ iRcs -> iRcs.createGroupThreadParticipantJoinedEvent(getTimestamp(),
+ getRcsGroupThread().getThreadId(), getOriginatingParticipant().getId(),
+ getJoinedParticipant().getId()));
+ }
+
+ public static final Creator<RcsGroupThreadParticipantJoinedEvent> CREATOR =
+ new Creator<RcsGroupThreadParticipantJoinedEvent>() {
+ @Override
+ public RcsGroupThreadParticipantJoinedEvent createFromParcel(Parcel in) {
+ return new RcsGroupThreadParticipantJoinedEvent(in);
+ }
+
+ @Override
+ public RcsGroupThreadParticipantJoinedEvent[] newArray(int size) {
+ return new RcsGroupThreadParticipantJoinedEvent[size];
+ }
+ };
+
+ protected RcsGroupThreadParticipantJoinedEvent(Parcel in) {
+ super(in);
+ mJoinedParticipantId = in.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mJoinedParticipantId);
+ }
+}
diff --git a/telephony/java/android/telephony/ims/RcsMessage.aidl b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEvent.aidl
similarity index 92%
rename from telephony/java/android/telephony/ims/RcsMessage.aidl
rename to telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEvent.aidl
index b32cd12..ff139ac 100644
--- a/telephony/java/android/telephony/ims/RcsMessage.aidl
+++ b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEvent.aidl
@@ -17,4 +17,4 @@
package android.telephony.ims;
-parcelable RcsMessage;
+parcelable RcsGroupThreadParticipantLeftEvent;
diff --git a/telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEvent.java b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEvent.java
new file mode 100644
index 0000000..87c1852
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsGroupThreadParticipantLeftEvent.java
@@ -0,0 +1,106 @@
+/*
+ * 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.telephony.ims;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+
+/**
+ * An event that indicates an RCS participant has left an {@link RcsThread}. Please see US6-23 -
+ * GSMA RCC.71 (RCS Universal Profile Service Definition Document)
+ *
+ * @hide - TODO(109759350) make this public
+ */
+public class RcsGroupThreadParticipantLeftEvent extends RcsGroupThreadEvent {
+ private int mLeavingParticipantId;
+
+ /**
+ * Creates a new {@link RcsGroupThreadParticipantLeftEvent}. his event is not persisted into
+ * storage until {@link RcsMessageStore#persistRcsEvent(RcsEvent)} is called.
+ *
+ * @param timestamp The timestamp of when this event happened, in milliseconds passed after
+ * midnight, January 1st, 1970 UTC
+ * @param rcsGroupThread The {@link RcsGroupThread} that this event happened on
+ * @param originatingParticipant The {@link RcsParticipant} that removed the
+ * {@link RcsParticipant} from the {@link RcsGroupThread}. It is
+ * possible that originatingParticipant and leavingParticipant are
+ * the same (i.e. {@link RcsParticipant} left the group
+ * themselves)
+ * @param leavingParticipant The {@link RcsParticipant} that left the {@link RcsGroupThread}
+ * @see RcsMessageStore#persistRcsEvent(RcsEvent)
+ */
+ public RcsGroupThreadParticipantLeftEvent(long timestamp,
+ @NonNull RcsGroupThread rcsGroupThread, @NonNull RcsParticipant originatingParticipant,
+ @NonNull RcsParticipant leavingParticipant) {
+ super(timestamp, rcsGroupThread.getThreadId(), originatingParticipant.getId());
+ mLeavingParticipantId = leavingParticipant.getId();
+ }
+
+ /**
+ * @hide - internal constructor for queries
+ */
+ public RcsGroupThreadParticipantLeftEvent(long timestamp, int rcsGroupThreadId,
+ int originatingParticipantId, int leavingParticipantId) {
+ super(timestamp, rcsGroupThreadId, originatingParticipantId);
+ mLeavingParticipantId = leavingParticipantId;
+ }
+
+ /**
+ * @return Returns the {@link RcsParticipant} that left the associated {@link RcsGroupThread}
+ * after this {@link RcsGroupThreadParticipantLeftEvent} happened.
+ */
+ @NonNull
+ public RcsParticipant getLeavingParticipantId() {
+ return new RcsParticipant(mLeavingParticipantId);
+ }
+
+ @Override
+ public void persist() throws RcsMessageStoreException {
+ RcsControllerCall.call(
+ iRcs -> iRcs.createGroupThreadParticipantJoinedEvent(getTimestamp(),
+ getRcsGroupThread().getThreadId(), getOriginatingParticipant().getId(),
+ getLeavingParticipantId().getId()));
+ }
+
+ public static final Creator<RcsGroupThreadParticipantLeftEvent> CREATOR =
+ new Creator<RcsGroupThreadParticipantLeftEvent>() {
+ @Override
+ public RcsGroupThreadParticipantLeftEvent createFromParcel(Parcel in) {
+ return new RcsGroupThreadParticipantLeftEvent(in);
+ }
+
+ @Override
+ public RcsGroupThreadParticipantLeftEvent[] newArray(int size) {
+ return new RcsGroupThreadParticipantLeftEvent[size];
+ }
+ };
+
+ protected RcsGroupThreadParticipantLeftEvent(Parcel in) {
+ super(in);
+ mLeavingParticipantId = in.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeInt(mLeavingParticipantId);
+ }
+}
diff --git a/telephony/java/android/telephony/ims/RcsIncomingMessage.aidl b/telephony/java/android/telephony/ims/RcsIncomingMessage.aidl
deleted file mode 100644
index 6552a82..0000000
--- a/telephony/java/android/telephony/ims/RcsIncomingMessage.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- *
- * Copyright 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.telephony.ims;
-
-parcelable RcsIncomingMessage;
diff --git a/telephony/java/android/telephony/ims/RcsIncomingMessage.java b/telephony/java/android/telephony/ims/RcsIncomingMessage.java
index f39e06d..2ea115b 100644
--- a/telephony/java/android/telephony/ims/RcsIncomingMessage.java
+++ b/telephony/java/android/telephony/ims/RcsIncomingMessage.java
@@ -15,34 +15,82 @@
*/
package android.telephony.ims;
-import android.os.Parcel;
+import android.annotation.WorkerThread;
/**
* This is a single instance of a message received over RCS.
- * @hide - TODO(sahinc) make this public
+ *
+ * @hide - TODO(109759350) make this public
*/
public class RcsIncomingMessage extends RcsMessage {
- public static final Creator<RcsIncomingMessage> CREATOR = new Creator<RcsIncomingMessage>() {
- @Override
- public RcsIncomingMessage createFromParcel(Parcel in) {
- return new RcsIncomingMessage(in);
- }
-
- @Override
- public RcsIncomingMessage[] newArray(int size) {
- return new RcsIncomingMessage[size];
- }
- };
-
- protected RcsIncomingMessage(Parcel in) {
+ /**
+ * @hide
+ */
+ RcsIncomingMessage(int id) {
+ super(id);
}
- @Override
- public int describeContents() {
- return 0;
+ /**
+ * Sets the timestamp of arrival for this message and persists into storage. The timestamp is
+ * defined as milliseconds passed after midnight, January 1, 1970 UTC
+ *
+ * @param arrivalTimestamp The timestamp to set to.
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setArrivalTimestamp(long arrivalTimestamp) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(
+ iRcs -> iRcs.setMessageArrivalTimestamp(mId, true, arrivalTimestamp));
}
+ /**
+ * @return Returns the timestamp of arrival for this message. The timestamp is defined as
+ * milliseconds passed after midnight, January 1, 1970 UTC
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @WorkerThread
+ public long getArrivalTimestamp() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getMessageArrivalTimestamp(mId, true));
+ }
+
+ /**
+ * Sets the timestamp of when the user saw this message and persists into storage. The timestamp
+ * is defined as milliseconds passed after midnight, January 1, 1970 UTC
+ *
+ * @param notifiedTimestamp The timestamp to set to.
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setSeenTimestamp(long notifiedTimestamp) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(
+ iRcs -> iRcs.setMessageSeenTimestamp(mId, true, notifiedTimestamp));
+ }
+
+ /**
+ * @return Returns the timestamp of when the user saw this message. The timestamp is defined as
+ * milliseconds passed after midnight, January 1, 1970 UTC
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @WorkerThread
+ public long getSeenTimestamp() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getMessageSeenTimestamp(mId, true));
+ }
+
+ /**
+ * @return Returns the sender of this incoming message.
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @WorkerThread
+ public RcsParticipant getSenderParticipant() throws RcsMessageStoreException {
+ return new RcsParticipant(
+ RcsControllerCall.call(iRcs -> iRcs.getSenderParticipant(mId)));
+ }
+
+ /**
+ * @return Returns {@code true} as this is an incoming message
+ */
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public boolean isIncoming() {
+ return true;
}
}
diff --git a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl b/telephony/java/android/telephony/ims/RcsIncomingMessageCreationParameters.aidl
similarity index 92%
copy from telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
copy to telephony/java/android/telephony/ims/RcsIncomingMessageCreationParameters.aidl
index 82d985d..76073c2 100644
--- a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
+++ b/telephony/java/android/telephony/ims/RcsIncomingMessageCreationParameters.aidl
@@ -17,4 +17,4 @@
package android.telephony.ims;
-parcelable RcsThreadIconChangedEvent;
+parcelable RcsIncomingMessageCreationParameters;
diff --git a/telephony/java/android/telephony/ims/RcsIncomingMessageCreationParameters.java b/telephony/java/android/telephony/ims/RcsIncomingMessageCreationParameters.java
new file mode 100644
index 0000000..acde8af
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsIncomingMessageCreationParameters.java
@@ -0,0 +1,180 @@
+/*
+ * 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.telephony.ims;
+
+import android.annotation.CheckResult;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * {@link RcsIncomingMessageCreationParameters} is a collection of parameters that should be passed
+ * into {@link RcsThread#addIncomingMessage(RcsIncomingMessageCreationParameters)} to generate an
+ * {@link RcsIncomingMessage} on that {@link RcsThread}
+ *
+ * @hide TODO:make public
+ */
+public class RcsIncomingMessageCreationParameters extends RcsMessageCreationParameters implements
+ Parcelable {
+ // The arrival timestamp for the RcsIncomingMessage to be created
+ private final long mArrivalTimestamp;
+ // The seen timestamp for the RcsIncomingMessage to be created
+ private final long mSeenTimestamp;
+ // The participant that sent this incoming message
+ private final int mSenderParticipantId;
+
+ /**
+ * Builder to help create an {@link RcsIncomingMessageCreationParameters}
+ *
+ * @see RcsThread#addIncomingMessage(RcsIncomingMessageCreationParameters)
+ */
+ public static class Builder extends RcsMessageCreationParameters.Builder {
+ private RcsParticipant mSenderParticipant;
+ private long mArrivalTimestamp;
+ private long mSeenTimestamp;
+
+ /**
+ * Creates a {@link Builder} to create an instance of
+ * {@link RcsIncomingMessageCreationParameters}
+ *
+ * @param originationTimestamp The timestamp of {@link RcsMessage} creation. The origination
+ * timestamp value in milliseconds passed after midnight,
+ * January 1, 1970 UTC
+ * @param arrivalTimestamp The timestamp of arrival, defined as milliseconds passed after
+ * midnight, January 1, 1970 UTC
+ * @param subscriptionId The subscription ID that was used to send or receive this
+ * {@link RcsMessage}
+ */
+ public Builder(long originationTimestamp, long arrivalTimestamp, int subscriptionId) {
+ super(originationTimestamp, subscriptionId);
+ mArrivalTimestamp = arrivalTimestamp;
+ }
+
+ /**
+ * Sets the {@link RcsParticipant} that send this {@link RcsIncomingMessage}
+ *
+ * @param senderParticipant The {@link RcsParticipant} that sent this
+ * {@link RcsIncomingMessage}
+ * @return The same instance of {@link Builder} to chain methods.
+ */
+ @CheckResult
+ public Builder setSenderParticipant(RcsParticipant senderParticipant) {
+ mSenderParticipant = senderParticipant;
+ return this;
+ }
+
+ /**
+ * Sets the time of the arrival of this {@link RcsIncomingMessage}
+
+ * @return The same instance of {@link Builder} to chain methods.
+ * @see RcsIncomingMessage#setArrivalTimestamp(long)
+ */
+ @CheckResult
+ public Builder setArrivalTimestamp(long arrivalTimestamp) {
+ mArrivalTimestamp = arrivalTimestamp;
+ return this;
+ }
+
+ /**
+ * Sets the time of the when this user saw the {@link RcsIncomingMessage}
+ * @param seenTimestamp The seen timestamp , defined as milliseconds passed after midnight,
+ * January 1, 1970 UTC
+ * @return The same instance of {@link Builder} to chain methods.
+ * @see RcsIncomingMessage#setSeenTimestamp(long)
+ */
+ @CheckResult
+ public Builder setSeenTimestamp(long seenTimestamp) {
+ mSeenTimestamp = seenTimestamp;
+ return this;
+ }
+
+ /**
+ * Creates parameters for creating a new incoming message.
+ * @return A new instance of {@link RcsIncomingMessageCreationParameters} to create a new
+ * {@link RcsIncomingMessage}
+ */
+ public RcsIncomingMessageCreationParameters build() {
+ return new RcsIncomingMessageCreationParameters(this);
+ }
+ }
+
+ private RcsIncomingMessageCreationParameters(Builder builder) {
+ super(builder);
+ mArrivalTimestamp = builder.mArrivalTimestamp;
+ mSeenTimestamp = builder.mSeenTimestamp;
+ mSenderParticipantId = builder.mSenderParticipant.getId();
+ }
+
+ protected RcsIncomingMessageCreationParameters(Parcel in) {
+ super(in);
+ mArrivalTimestamp = in.readLong();
+ mSeenTimestamp = in.readLong();
+ mSenderParticipantId = in.readInt();
+ }
+
+ /**
+ * @return Returns the arrival timestamp for the {@link RcsIncomingMessage} to be created.
+ * Timestamp is defined as milliseconds passed after midnight, January 1, 1970 UTC
+ */
+ public long getArrivalTimestamp() {
+ return mArrivalTimestamp;
+ }
+
+ /**
+ * @return Returns the seen timestamp for the {@link RcsIncomingMessage} to be created.
+ * Timestamp is defined as milliseconds passed after midnight, January 1, 1970 UTC
+ */
+ public long getSeenTimestamp() {
+ return mSeenTimestamp;
+ }
+
+ /**
+ * Helper getter for {@link com.android.internal.telephony.ims.RcsMessageStoreController} to
+ * create {@link RcsIncomingMessage}s
+ *
+ * Since the API doesn't expose any ID's to API users, this should be hidden.
+ * @hide
+ */
+ public int getSenderParticipantId() {
+ return mSenderParticipantId;
+ }
+
+ public static final Creator<RcsIncomingMessageCreationParameters> CREATOR =
+ new Creator<RcsIncomingMessageCreationParameters>() {
+ @Override
+ public RcsIncomingMessageCreationParameters createFromParcel(Parcel in) {
+ return new RcsIncomingMessageCreationParameters(in);
+ }
+
+ @Override
+ public RcsIncomingMessageCreationParameters[] newArray(int size) {
+ return new RcsIncomingMessageCreationParameters[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeLong(mArrivalTimestamp);
+ dest.writeLong(mSeenTimestamp);
+ dest.writeInt(mSenderParticipantId);
+ }
+}
diff --git a/telephony/java/android/telephony/ims/RcsLocationPart.aidl b/telephony/java/android/telephony/ims/RcsLocationPart.aidl
deleted file mode 100644
index 4fe5ca9..0000000
--- a/telephony/java/android/telephony/ims/RcsLocationPart.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- *
- * Copyright 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.telephony.ims;
-
-parcelable RcsLocationPart;
diff --git a/telephony/java/android/telephony/ims/RcsLocationPart.java b/telephony/java/android/telephony/ims/RcsLocationPart.java
deleted file mode 100644
index 19be4ce..0000000
--- a/telephony/java/android/telephony/ims/RcsLocationPart.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.telephony.ims;
-
-import android.os.Parcel;
-
-/**
- * A part of a composite {@link RcsMessage} that holds a location
- * @hide - TODO(sahinc) make this public
- */
-public class RcsLocationPart extends RcsPart {
- public static final Creator<RcsLocationPart> CREATOR = new Creator<RcsLocationPart>() {
- @Override
- public RcsLocationPart createFromParcel(Parcel in) {
- return new RcsLocationPart(in);
- }
-
- @Override
- public RcsLocationPart[] newArray(int size) {
- return new RcsLocationPart[size];
- }
- };
-
- protected RcsLocationPart(Parcel in) {
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- }
-}
diff --git a/telephony/java/android/telephony/ims/RcsManager.aidl b/telephony/java/android/telephony/ims/RcsManager.aidl
deleted file mode 100644
index 63bc71c..0000000
--- a/telephony/java/android/telephony/ims/RcsManager.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- *
- * Copyright 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.telephony.ims;
-
-parcelable RcsManager;
diff --git a/telephony/java/android/telephony/ims/RcsManager.java b/telephony/java/android/telephony/ims/RcsManager.java
index df108c8..e84d4ed 100644
--- a/telephony/java/android/telephony/ims/RcsManager.java
+++ b/telephony/java/android/telephony/ims/RcsManager.java
@@ -28,7 +28,7 @@
private static final RcsMessageStore sRcsMessageStoreInstance = new RcsMessageStore();
/**
- * Returns an instance of RcsMessageStore.
+ * Returns an instance of {@link RcsMessageStore}
*/
public RcsMessageStore getRcsMessageStore() {
return sRcsMessageStoreInstance;
diff --git a/telephony/java/android/telephony/ims/RcsMessage.java b/telephony/java/android/telephony/ims/RcsMessage.java
index d46685c..b70a9a7 100644
--- a/telephony/java/android/telephony/ims/RcsMessage.java
+++ b/telephony/java/android/telephony/ims/RcsMessage.java
@@ -15,11 +15,314 @@
*/
package android.telephony.ims;
-import android.os.Parcelable;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.WorkerThread;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
/**
* This is a single instance of a message sent or received over RCS.
- * @hide - TODO(sahinc) make this public
+ *
+ * @hide - TODO(109759350) make this public
*/
-public abstract class RcsMessage implements Parcelable {
+public abstract class RcsMessage {
+ /**
+ * The value to indicate that this {@link RcsMessage} does not have any location information.
+ */
+ public static final double LOCATION_NOT_SET = Double.MIN_VALUE;
+
+ /**
+ * The status to indicate that this {@link RcsMessage}s status is not set yet.
+ */
+ public static final int NOT_SET = 0;
+
+ /**
+ * The status to indicate that this {@link RcsMessage} is a draft and is not in the process of
+ * sending yet.
+ */
+ public static final int DRAFT = 1;
+
+ /**
+ * The status to indicate that this {@link RcsMessage} was successfully sent.
+ */
+ public static final int QUEUED = 2;
+
+ /**
+ * The status to indicate that this {@link RcsMessage} is actively being sent.
+ */
+ public static final int SENDING = 3;
+
+ /**
+ * The status to indicate that this {@link RcsMessage} was successfully sent.
+ */
+ public static final int SENT = 4;
+
+ /**
+ * The status to indicate that this {@link RcsMessage} failed to send in an attempt before, and
+ * now being retried.
+ */
+ public static final int RETRYING = 5;
+
+ /**
+ * The status to indicate that this {@link RcsMessage} has permanently failed to send.
+ */
+ public static final int FAILED = 6;
+
+ /**
+ * The status to indicate that this {@link RcsMessage} was successfully received.
+ */
+ public static final int RECEIVED = 7;
+
+ /**
+ * The status to indicate that this {@link RcsMessage} was seen.
+ */
+ public static final int SEEN = 9;
+
+ protected int mId;
+
+ @IntDef({
+ DRAFT, QUEUED, SENDING, SENT, RETRYING, FAILED, RECEIVED, SEEN
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface RcsMessageStatus {
+ }
+
+ RcsMessage(int id) {
+ mId = id;
+ }
+
+ /**
+ * Returns the row Id from the common message.
+ *
+ * @hide
+ */
+ public int getId() {
+ return mId;
+ }
+
+ /**
+ * @return Returns the subscription ID that this {@link RcsMessage} was sent from, or delivered
+ * to.
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ * @see android.telephony.SubscriptionInfo#getSubscriptionId
+ */
+ public int getSubscriptionId() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getMessageSubId(mId, isIncoming()));
+ }
+
+ /**
+ * Sets the subscription ID that this {@link RcsMessage} was sent from, or delivered to and
+ * persists it into storage.
+ *
+ * @param subId The subscription ID to persists into storage.
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ * @see android.telephony.SubscriptionInfo#getSubscriptionId
+ */
+ @WorkerThread
+ public void setSubscriptionId(int subId) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setMessageSubId(mId, isIncoming(), subId));
+ }
+
+ /**
+ * Sets the status of this message and persists it into storage. Please see
+ * {@link RcsFileTransferPart#setFileTransferStatus(int)} to set statuses around file transfers.
+ *
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setStatus(@RcsMessageStatus int rcsMessageStatus) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(
+ iRcs -> iRcs.setMessageStatus(mId, isIncoming(), rcsMessageStatus));
+ }
+
+ /**
+ * @return Returns the status of this message. Please see
+ * {@link RcsFileTransferPart#setFileTransferStatus(int)} to set statuses around file transfers.
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @WorkerThread
+ public @RcsMessageStatus int getStatus() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getMessageStatus(mId, isIncoming()));
+ }
+
+ /**
+ * Sets the origination timestamp of this message and persists it into storage. Origination is
+ * defined as when the sender tapped the send button.
+ *
+ * @param timestamp The origination timestamp value in milliseconds passed after midnight,
+ * January 1, 1970 UTC
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setOriginationTimestamp(long timestamp) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(
+ iRcs -> iRcs.setMessageOriginationTimestamp(mId, isIncoming(), timestamp));
+ }
+
+ /**
+ * @return Returns the origination timestamp of this message in milliseconds passed after
+ * midnight, January 1, 1970 UTC. Origination is defined as when the sender tapped the send
+ * button.
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @WorkerThread
+ public long getOriginationTimestamp() throws RcsMessageStoreException {
+ return RcsControllerCall.call(
+ iRcs -> iRcs.getMessageOriginationTimestamp(mId, isIncoming()));
+ }
+
+ /**
+ * Sets the globally unique RCS message identifier for this message and persists it into
+ * storage. This function does not confirm that this message id is unique. Please see 4.4.5.2
+ * - GSMA RCC.53 (RCS Device API 1.6 Specification
+ *
+ * @param rcsMessageGlobalId The globally RCS message identifier
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setRcsMessageId(String rcsMessageGlobalId) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(
+ iRcs -> iRcs.setGlobalMessageIdForMessage(mId, isIncoming(), rcsMessageGlobalId));
+ }
+
+ /**
+ * @return Returns the globally unique RCS message identifier for this message. Please see
+ * 4.4.5.2 - GSMA RCC.53 (RCS Device API 1.6 Specification
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @WorkerThread
+ public String getRcsMessageId() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getGlobalMessageIdForMessage(mId, isIncoming()));
+ }
+
+ /**
+ * @return Returns the user visible text included in this message.
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @WorkerThread
+ public String getText() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getTextForMessage(mId, isIncoming()));
+ }
+
+ /**
+ * Sets the user visible text for this message and persists in storage.
+ *
+ * @param text The text this message now has
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setText(String text) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setTextForMessage(mId, isIncoming(), text));
+ }
+
+ /**
+ * @return Returns the associated latitude for this message, or
+ * {@link RcsMessage#LOCATION_NOT_SET} if it does not contain a location.
+ *
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @WorkerThread
+ public double getLatitude() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getLatitudeForMessage(mId, isIncoming()));
+ }
+
+ /**
+ * Sets the latitude for this message and persists in storage.
+ *
+ * @param latitude The latitude for this location message.
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setLatitude(double latitude) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(
+ iRcs -> iRcs.setLatitudeForMessage(mId, isIncoming(), latitude));
+ }
+
+ /**
+ * @return Returns the associated longitude for this message, or
+ * {@link RcsMessage#LOCATION_NOT_SET} if it does not contain a location.
+ *
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @WorkerThread
+ public double getLongitude() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getLongitudeForMessage(mId, isIncoming()));
+ }
+
+ /**
+ * Sets the longitude for this message and persists in storage.
+ *
+ * @param longitude The longitude for this location message.
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setLongitude(double longitude) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(
+ iRcs -> iRcs.setLongitudeForMessage(mId, isIncoming(), longitude));
+ }
+
+ /**
+ * Attaches an {@link RcsFileTransferPart} to this message and persists into storage.
+ *
+ * @param fileTransferCreationParameters The parameters to be used to create the
+ * {@link RcsFileTransferPart}
+ * @return A new instance of {@link RcsFileTransferPart}
+ * @throws RcsMessageStoreException if the file transfer could not be persisted into storage.
+ */
+ @NonNull
+ @WorkerThread
+ public RcsFileTransferPart insertFileTransfer(
+ RcsFileTransferCreationParameters fileTransferCreationParameters)
+ throws RcsMessageStoreException {
+ return new RcsFileTransferPart(RcsControllerCall.call(
+ iRcs -> iRcs.storeFileTransfer(mId, isIncoming(), fileTransferCreationParameters)));
+ }
+
+ /**
+ * @return Returns all the {@link RcsFileTransferPart}s associated with this message in an
+ * unmodifiable set.
+ * @throws RcsMessageStoreException if the file transfers could not be read from the storage
+ */
+ @NonNull
+ @WorkerThread
+ public Set<RcsFileTransferPart> getFileTransferParts() throws RcsMessageStoreException {
+ Set<RcsFileTransferPart> fileTransferParts = new HashSet<>();
+
+ int[] fileTransferIds = RcsControllerCall.call(
+ iRcs -> iRcs.getFileTransfersAttachedToMessage(mId, isIncoming()));
+
+ for (int fileTransfer : fileTransferIds) {
+ fileTransferParts.add(new RcsFileTransferPart(fileTransfer));
+ }
+
+ return Collections.unmodifiableSet(fileTransferParts);
+ }
+
+ /**
+ * Removes a {@link RcsFileTransferPart} from this message, and deletes it in storage.
+ *
+ * @param fileTransferPart The part to delete.
+ * @throws RcsMessageStoreException if the file transfer could not be removed from storage
+ */
+ @WorkerThread
+ public void removeFileTransferPart(@NonNull RcsFileTransferPart fileTransferPart)
+ throws RcsMessageStoreException {
+ if (fileTransferPart == null) {
+ return;
+ }
+
+ RcsControllerCall.callWithNoReturn(
+ iRcs -> iRcs.deleteFileTransfer(fileTransferPart.getId()));
+ }
+
+ /**
+ * @return Returns {@code true} if this message was received on this device, {@code false} if it
+ * was sent.
+ */
+ public abstract boolean isIncoming();
}
diff --git a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl b/telephony/java/android/telephony/ims/RcsMessageCreationParameters.aidl
similarity index 93%
copy from telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
copy to telephony/java/android/telephony/ims/RcsMessageCreationParameters.aidl
index 82d985d..5774d00 100644
--- a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
+++ b/telephony/java/android/telephony/ims/RcsMessageCreationParameters.aidl
@@ -17,4 +17,4 @@
package android.telephony.ims;
-parcelable RcsThreadIconChangedEvent;
+parcelable RcsMessageCreationParameters;
diff --git a/telephony/java/android/telephony/ims/RcsMessageCreationParameters.java b/telephony/java/android/telephony/ims/RcsMessageCreationParameters.java
new file mode 100644
index 0000000..ff3f33e
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsMessageCreationParameters.java
@@ -0,0 +1,265 @@
+/*
+ * 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.telephony.ims;
+
+import static android.telephony.ims.RcsMessage.LOCATION_NOT_SET;
+
+import android.annotation.CheckResult;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.SubscriptionInfo;
+
+/**
+ * The collection of parameters to be passed into
+ * {@link RcsThread#addIncomingMessage(RcsIncomingMessageCreationParameters)} and
+ * {@link RcsThread#addOutgoingMessage(RcsMessageCreationParameters)} to create and persist
+ * {@link RcsMessage}s on an {@link RcsThread}
+ *
+ * @hide TODO - make public
+ */
+public class RcsMessageCreationParameters implements Parcelable {
+ // The globally unique id of the RcsMessage to be created.
+ private final String mRcsMessageGlobalId;
+
+ // The subscription that this message was/will be received/sent from.
+ private final int mSubId;
+ // The sending/receiving status of the message
+ private final @RcsMessage.RcsMessageStatus int mMessageStatus;
+ // The timestamp of message creation
+ private final long mOriginationTimestamp;
+ // The user visible content of the message
+ private final String mText;
+ // The latitude of the message if this is a location message
+ private final double mLatitude;
+ // The longitude of the message if this is a location message
+ private final double mLongitude;
+
+ /**
+ * @return Returns the globally unique RCS Message ID for the {@link RcsMessage} to be created.
+ * Please see 4.4.5.2 - GSMA RCC.53 (RCS Device API 1.6 Specification
+ */
+ @Nullable
+ public String getRcsMessageGlobalId() {
+ return mRcsMessageGlobalId;
+ }
+
+ /**
+ * @return Returns the subscription ID that was used to send or receive the {@link RcsMessage}
+ * to be created.
+ */
+ public int getSubId() {
+ return mSubId;
+ }
+
+ /**
+ * @return Returns the status for the {@link RcsMessage} to be created.
+ * @see RcsMessage.RcsMessageStatus
+ */
+ public int getMessageStatus() {
+ return mMessageStatus;
+ }
+
+ /**
+ * @return Returns the origination timestamp of the {@link RcsMessage} to be created in
+ * milliseconds passed after midnight, January 1, 1970 UTC. Origination is defined as when
+ * the sender tapped the send button.
+ */
+ public long getOriginationTimestamp() {
+ return mOriginationTimestamp;
+ }
+
+ /**
+ * @return Returns the user visible text contained in the {@link RcsMessage} to be created
+ */
+ @Nullable
+ public String getText() {
+ return mText;
+ }
+
+ /**
+ * @return Returns the latitude of the {@link RcsMessage} to be created, or
+ * {@link RcsMessage#LOCATION_NOT_SET} if the message does not contain a location.
+ */
+ public double getLatitude() {
+ return mLatitude;
+ }
+
+ /**
+ * @return Returns the longitude of the {@link RcsMessage} to be created, or
+ * {@link RcsMessage#LOCATION_NOT_SET} if the message does not contain a location.
+ */
+ public double getLongitude() {
+ return mLongitude;
+ }
+
+ /**
+ * The base builder for creating {@link RcsMessage}s on {@link RcsThread}s.
+ *
+ * @see RcsIncomingMessageCreationParameters
+ */
+ public static class Builder {
+ private String mRcsMessageGlobalId;
+ private int mSubId;
+ private @RcsMessage.RcsMessageStatus int mMessageStatus;
+ private long mOriginationTimestamp;
+ private String mText;
+ private double mLatitude = LOCATION_NOT_SET;
+ private double mLongitude = LOCATION_NOT_SET;
+
+ /**
+ * Creates a new {@link Builder} to create an instance of
+ * {@link RcsMessageCreationParameters}.
+ *
+ * @param originationTimestamp The timestamp of {@link RcsMessage} creation. The origination
+ * timestamp value in milliseconds passed after midnight,
+ * January 1, 1970 UTC
+ * @param subscriptionId The subscription ID that was used to send or receive this
+ * {@link RcsMessage}
+ * @see SubscriptionInfo#getSubscriptionId()
+ */
+ public Builder(long originationTimestamp, int subscriptionId) {
+ mOriginationTimestamp = originationTimestamp;
+ mSubId = subscriptionId;
+ }
+
+ /**
+ * Sets the status of the {@link RcsMessage} to be built.
+ *
+ * @param rcsMessageStatus The status to be set
+ * @return The same instance of {@link Builder} to chain methods
+ * @see RcsMessage#setStatus(int)
+ */
+ @CheckResult
+ public Builder setStatus(@RcsMessage.RcsMessageStatus int rcsMessageStatus) {
+ mMessageStatus = rcsMessageStatus;
+ return this;
+ }
+
+ /**
+ * Sets the globally unique RCS message identifier for the {@link RcsMessage} to be built.
+ * This function does not confirm that this message id is unique. Please see 4.4.5.2 - GSMA
+ * RCC.53 (RCS Device API 1.6 Specification)
+ *
+ * @param rcsMessageId The ID to be set
+ * @return The same instance of {@link Builder} to chain methods
+ * @see RcsMessage#setRcsMessageId(String)
+ */
+ @CheckResult
+ public Builder setRcsMessageId(String rcsMessageId) {
+ mRcsMessageGlobalId = rcsMessageId;
+ return this;
+ }
+
+ /**
+ * Sets the text of the {@link RcsMessage} to be built.
+ *
+ * @param text The user visible text of the message
+ * @return The same instance of {@link Builder} to chain methods
+ * @see RcsMessage#setText(String)
+ */
+ @CheckResult
+ public Builder setText(String text) {
+ mText = text;
+ return this;
+ }
+
+ /**
+ * Sets the latitude of the {@link RcsMessage} to be built. Please see US5-24 - GSMA RCC.71
+ * (RCS Universal Profile Service Definition Document)
+ *
+ * @param latitude The latitude of the location information associated with this message.
+ * @return The same instance of {@link Builder} to chain methods
+ * @see RcsMessage#setLatitude(double)
+ */
+ @CheckResult
+ public Builder setLatitude(double latitude) {
+ mLatitude = latitude;
+ return this;
+ }
+
+ /**
+ * Sets the longitude of the {@link RcsMessage} to be built. Please see US5-24 - GSMA RCC.71
+ * (RCS Universal Profile Service Definition Document)
+ *
+ * @param longitude The longitude of the location information associated with this message.
+ * @return The same instance of {@link Builder} to chain methods
+ * @see RcsMessage#setLongitude(double)
+ */
+ @CheckResult
+ public Builder setLongitude(double longitude) {
+ mLongitude = longitude;
+ return this;
+ }
+
+ /**
+ * @hide
+ */
+ public RcsMessageCreationParameters build() {
+ return new RcsMessageCreationParameters(this);
+ }
+ }
+
+ protected RcsMessageCreationParameters(Builder builder) {
+ mRcsMessageGlobalId = builder.mRcsMessageGlobalId;
+ mSubId = builder.mSubId;
+ mMessageStatus = builder.mMessageStatus;
+ mOriginationTimestamp = builder.mOriginationTimestamp;
+ mText = builder.mText;
+ mLatitude = builder.mLatitude;
+ mLongitude = builder.mLongitude;
+ }
+
+ protected RcsMessageCreationParameters(Parcel in) {
+ mRcsMessageGlobalId = in.readString();
+ mSubId = in.readInt();
+ mMessageStatus = in.readInt();
+ mOriginationTimestamp = in.readLong();
+ mText = in.readString();
+ mLatitude = in.readDouble();
+ mLongitude = in.readDouble();
+ }
+
+ public static final Creator<RcsMessageCreationParameters> CREATOR =
+ new Creator<RcsMessageCreationParameters>() {
+ @Override
+ public RcsMessageCreationParameters createFromParcel(Parcel in) {
+ return new RcsMessageCreationParameters(in);
+ }
+
+ @Override
+ public RcsMessageCreationParameters[] newArray(int size) {
+ return new RcsMessageCreationParameters[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mRcsMessageGlobalId);
+ dest.writeInt(mSubId);
+ dest.writeInt(mMessageStatus);
+ dest.writeLong(mOriginationTimestamp);
+ dest.writeString(mText);
+ dest.writeDouble(mLatitude);
+ dest.writeDouble(mLongitude);
+ }
+}
diff --git a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl b/telephony/java/android/telephony/ims/RcsMessageQueryParameters.aidl
similarity index 94%
copy from telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
copy to telephony/java/android/telephony/ims/RcsMessageQueryParameters.aidl
index 82d985d..c325c23 100644
--- a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
+++ b/telephony/java/android/telephony/ims/RcsMessageQueryParameters.aidl
@@ -17,4 +17,4 @@
package android.telephony.ims;
-parcelable RcsThreadIconChangedEvent;
+parcelable RcsMessageQueryParameters;
diff --git a/telephony/java/android/telephony/ims/RcsMessageQueryParameters.java b/telephony/java/android/telephony/ims/RcsMessageQueryParameters.java
new file mode 100644
index 0000000..c964cf9
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsMessageQueryParameters.java
@@ -0,0 +1,361 @@
+/*
+ * 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.telephony.ims;
+
+import android.annotation.CheckResult;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.security.InvalidParameterException;
+
+/**
+ * The parameters to pass into
+ * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParameters)} in order to select a
+ * subset of {@link RcsMessage}s present in the message store.
+ *
+ * @hide TODO - make the Builder and builder() public. The rest should stay internal only.
+ */
+public class RcsMessageQueryParameters implements Parcelable {
+ /**
+ * @hide - not meant for public use
+ */
+ public static final int THREAD_ID_NOT_SET = -1;
+
+ /**
+ * Flag to be used with {@link Builder#setSortProperty(int)} to denote that the results should
+ * be sorted in the same order of {@link RcsMessage}s that got persisted into storage for faster
+ * results.
+ */
+ public static final int SORT_BY_CREATION_ORDER = 0;
+
+ /**
+ * Flag to be used with {@link Builder#setSortProperty(int)} to denote that the results should
+ * be sorted according to the timestamp of {@link RcsMessage#getOriginationTimestamp()}
+ */
+ public static final int SORT_BY_TIMESTAMP = 1;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SORT_BY_CREATION_ORDER, SORT_BY_TIMESTAMP})
+ public @interface SortingProperty {
+ }
+
+ /**
+ * Bitmask flag to be used with {@link Builder#setMessageType(int)} to make
+ * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParameters)} return
+ * {@link RcsIncomingMessage}s.
+ */
+ public static final int MESSAGE_TYPE_INCOMING = 0x0001;
+
+ /**
+ * Bitmask flag to be used with {@link Builder#setMessageType(int)} to make
+ * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParameters)} return
+ * {@link RcsOutgoingMessage}s.
+ */
+ public static final int MESSAGE_TYPE_OUTGOING = 0x0002;
+
+ /**
+ * Bitmask flag to be used with {@link Builder#setFileTransferPresence(int)} to make
+ * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParameters)} return {@link RcsMessage}s
+ * that have an {@link RcsFileTransferPart} attached.
+ */
+ public static final int MESSAGES_WITH_FILE_TRANSFERS = 0x0004;
+
+ /**
+ * Bitmask flag to be used with {@link Builder#setFileTransferPresence(int)} to make
+ * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParameters)} return {@link RcsMessage}s
+ * that don't have an {@link RcsFileTransferPart} attached.
+ */
+ public static final int MESSAGES_WITHOUT_FILE_TRANSFERS = 0x0008;
+
+ /**
+ * @hide - not meant for public use
+ */
+ public static final String MESSAGE_QUERY_PARAMETERS_KEY = "message_query_parameters";
+
+ // Whether the result should be filtered against incoming or outgoing messages
+ private int mMessageType;
+ // Whether the result should have file transfer messages attached or not
+ private int mFileTransferPresence;
+ // The SQL "Like" clause to filter messages
+ private String mMessageLike;
+ // The property the messages should be sorted against
+ private @SortingProperty int mSortingProperty;
+ // Whether the messages should be sorted in ascending order
+ private boolean mIsAscending;
+ // The number of results that should be returned with this query
+ private int mLimit;
+ // The thread that the results should be limited to
+ private int mThreadId;
+
+ RcsMessageQueryParameters(int messageType, int fileTransferPresence, String messageLike,
+ int threadId, @SortingProperty int sortingProperty, boolean isAscending, int limit) {
+ mMessageType = messageType;
+ mFileTransferPresence = fileTransferPresence;
+ mMessageLike = messageLike;
+ mSortingProperty = sortingProperty;
+ mIsAscending = isAscending;
+ mLimit = limit;
+ mThreadId = threadId;
+ }
+
+ /**
+ * @return Returns the type of {@link RcsMessage}s that this {@link RcsMessageQueryParameters}
+ * is set to query for.
+ */
+ public int getMessageType() {
+ return mMessageType;
+ }
+
+ /**
+ * @return Returns whether the result query should return {@link RcsMessage}s with
+ * {@link RcsFileTransferPart}s or not
+ */
+ public int getFileTransferPresence() {
+ return mFileTransferPresence;
+ }
+
+ /**
+ * @return Returns the SQL-inspired "LIKE" clause that will be used to match {@link RcsMessage}s
+ */
+ public String getMessageLike() {
+ return mMessageLike;
+ }
+
+ /**
+ * @return Returns the number of {@link RcsThread}s to be returned from the query. A value of
+ * 0 means there is no set limit.
+ */
+ public int getLimit() {
+ return mLimit;
+ }
+
+ /**
+ * @return Returns the property that will be used to sort the result against.
+ * @see SortingProperty
+ */
+ public @SortingProperty int getSortingProperty() {
+ return mSortingProperty;
+ }
+
+ /**
+ * @return Returns {@code true} if the result set will be sorted in ascending order,
+ * {@code false} if it will be sorted in descending order.
+ */
+ public boolean getSortDirection() {
+ return mIsAscending;
+ }
+
+ /**
+ * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to get
+ * the thread that the result query should be limited to.
+ *
+ * As we do not expose any sort of integer ID's to public usage, this should be hidden.
+ *
+ * @hide - not meant for public use
+ */
+ public int getThreadId() {
+ return mThreadId;
+ }
+
+ /**
+ * A helper class to build the {@link RcsMessageQueryParameters}.
+ */
+ public static class Builder {
+ private @SortingProperty int mSortingProperty;
+ private int mMessageType;
+ private int mFileTransferPresence;
+ private String mMessageLike;
+ private boolean mIsAscending;
+ private int mLimit = 100;
+ private int mThreadId = THREAD_ID_NOT_SET;
+
+ /**
+ * Creates a new builder for {@link RcsMessageQueryParameters} to be used in
+ * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParameters)}
+ *
+ */
+ public Builder() {
+ // empty implementation
+ }
+
+ /**
+ * Desired number of threads to be returned from the query. Passing in 0 will return all
+ * existing threads at once. The limit defaults to 100.
+ *
+ * @param limit The number to limit the query result to.
+ * @return The same instance of the builder to chain parameters.
+ * @throws InvalidParameterException If the given limit is negative.
+ */
+ @CheckResult
+ public Builder setResultLimit(@IntRange(from = 0) int limit)
+ throws InvalidParameterException {
+ if (limit < 0) {
+ throw new InvalidParameterException("The query limit must be non-negative");
+ }
+
+ mLimit = limit;
+ return this;
+ }
+
+ /**
+ * Sets the type of messages to be returned from the query.
+ *
+ * @param messageType The type of message to be returned.
+ * @return The same instance of the builder to chain parameters.
+ * @see RcsMessageQueryParameters#MESSAGE_TYPE_INCOMING
+ * @see RcsMessageQueryParameters#MESSAGE_TYPE_OUTGOING
+ */
+ @CheckResult
+ public Builder setMessageType(int messageType) {
+ mMessageType = messageType;
+ return this;
+ }
+
+ /**
+ * Sets whether file transfer messages should be included in the query result or not.
+ *
+ * @param fileTransferPresence Whether file transfers should be included in the result
+ * @return The same instance of the builder to chain parameters.
+ * @see RcsMessageQueryParameters#MESSAGES_WITH_FILE_TRANSFERS
+ * @see RcsMessageQueryParameters#MESSAGES_WITHOUT_FILE_TRANSFERS
+ */
+ @CheckResult
+ public Builder setFileTransferPresence(int fileTransferPresence) {
+ mFileTransferPresence = fileTransferPresence;
+ return this;
+ }
+
+ /**
+ * Sets an SQL-inspired "like" clause to match with messages. Using a percent sign ('%')
+ * wildcard matches any sequence of zero or more characters. Using an underscore ('_')
+ * wildcard matches any single character. Not using any wildcards would only perform a
+ * string match. The input string is case-insensitive.
+ *
+ * The input "Wh%" would match messages "who", "where" and "what", while the input "Wh_"
+ * would only match "who"
+ *
+ * @param messageLike The "like" clause for matching {@link RcsMessage}s.
+ * @return The same instance of the builder to chain parameters.
+ */
+ @CheckResult
+ public Builder setMessageLike(String messageLike) {
+ mMessageLike = messageLike;
+ return this;
+ }
+
+ /**
+ * Sets the property where the results should be sorted against. Defaults to
+ * {@link RcsMessageQueryParameters.SortingProperty#SORT_BY_CREATION_ORDER}
+ *
+ * @param sortingProperty against which property the results should be sorted
+ * @return The same instance of the builder to chain parameters.
+ */
+ @CheckResult
+ public Builder setSortProperty(@SortingProperty int sortingProperty) {
+ mSortingProperty = sortingProperty;
+ return this;
+ }
+
+ /**
+ * Sets whether the results should be sorted ascending or descending
+ *
+ * @param isAscending whether the results should be sorted ascending
+ * @return The same instance of the builder to chain parameters.
+ */
+ @CheckResult
+ public Builder setSortDirection(boolean isAscending) {
+ mIsAscending = isAscending;
+ return this;
+ }
+
+ /**
+ * Limits the results to the given thread.
+ *
+ * @param thread the {@link RcsThread} that results should be limited to. If set to
+ * {@code null}, messages on all threads will be queried
+ * @return The same instance of the builder to chain parameters.
+ */
+ @CheckResult
+ public Builder setThread(@Nullable RcsThread thread) {
+ if (thread == null) {
+ mThreadId = THREAD_ID_NOT_SET;
+ } else {
+ mThreadId = thread.getThreadId();
+ }
+ return this;
+ }
+
+ /**
+ * Builds the {@link RcsMessageQueryParameters} to use in
+ * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParameters)}
+ *
+ * @return An instance of {@link RcsMessageQueryParameters} to use with the message
+ * query.
+ */
+ public RcsMessageQueryParameters build() {
+ return new RcsMessageQueryParameters(mMessageType, mFileTransferPresence, mMessageLike,
+ mThreadId, mSortingProperty, mIsAscending, mLimit);
+ }
+ }
+
+ /**
+ * Parcelable boilerplate below.
+ */
+ protected RcsMessageQueryParameters(Parcel in) {
+ mMessageType = in.readInt();
+ mFileTransferPresence = in.readInt();
+ mMessageLike = in.readString();
+ mSortingProperty = in.readInt();
+ mIsAscending = in.readBoolean();
+ mLimit = in.readInt();
+ mThreadId = in.readInt();
+ }
+
+ public static final Creator<RcsMessageQueryParameters> CREATOR =
+ new Creator<RcsMessageQueryParameters>() {
+ @Override
+ public RcsMessageQueryParameters createFromParcel(Parcel in) {
+ return new RcsMessageQueryParameters(in);
+ }
+
+ @Override
+ public RcsMessageQueryParameters[] newArray(int size) {
+ return new RcsMessageQueryParameters[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mMessageType);
+ dest.writeInt(mFileTransferPresence);
+ dest.writeString(mMessageLike);
+ dest.writeInt(mSortingProperty);
+ dest.writeBoolean(mIsAscending);
+ dest.writeInt(mLimit);
+ dest.writeInt(mThreadId);
+ }
+}
diff --git a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl b/telephony/java/android/telephony/ims/RcsMessageQueryResult.aidl
similarity index 94%
copy from telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
copy to telephony/java/android/telephony/ims/RcsMessageQueryResult.aidl
index 82d985d..a73ba50 100644
--- a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
+++ b/telephony/java/android/telephony/ims/RcsMessageQueryResult.aidl
@@ -17,4 +17,4 @@
package android.telephony.ims;
-parcelable RcsThreadIconChangedEvent;
+parcelable RcsMessageQueryResult;
diff --git a/telephony/java/android/telephony/ims/RcsMessageQueryResult.java b/telephony/java/android/telephony/ims/RcsMessageQueryResult.java
new file mode 100644
index 0000000..c3846fd
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsMessageQueryResult.java
@@ -0,0 +1,115 @@
+/*
+ * 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.telephony.ims;
+
+import static android.provider.Telephony.RcsColumns.RcsUnifiedMessageColumns.MESSAGE_TYPE_INCOMING;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.ims.RcsTypeIdPair;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The result of a {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParameters)}
+ * call. This class allows getting the token for querying the next batch of messages in order to
+ * prevent handling large amounts of data at once.
+ *
+ * @hide
+ */
+public class RcsMessageQueryResult implements Parcelable {
+ // The token to continue the query to get the next batch of results
+ private RcsQueryContinuationToken mContinuationToken;
+ // The message type and message ID pairs for all the messages in this query result
+ private List<RcsTypeIdPair> mMessageTypeIdPairs;
+
+ /**
+ * Internal constructor for {@link com.android.internal.telephony.ims.RcsMessageStoreController}
+ * to create query results
+ *
+ * @hide
+ */
+ public RcsMessageQueryResult(
+ RcsQueryContinuationToken continuationToken,
+ List<RcsTypeIdPair> messageTypeIdPairs) {
+ mContinuationToken = continuationToken;
+ mMessageTypeIdPairs = messageTypeIdPairs;
+ }
+
+ /**
+ * Returns a token to call
+ * {@link RcsMessageStore#getRcsMessages(RcsQueryContinuationToken)}
+ * to get the next batch of {@link RcsMessage}s.
+ */
+ @Nullable
+ public RcsQueryContinuationToken getContinuationToken() {
+ return mContinuationToken;
+ }
+
+ /**
+ * Returns all the {@link RcsMessage}s in the current query result. Call {@link
+ * RcsMessageStore#getRcsMessages(RcsQueryContinuationToken)} to get the next batch
+ * of {@link RcsMessage}s.
+ */
+ @NonNull
+ public List<RcsMessage> getMessages() {
+ List<RcsMessage> messages = new ArrayList<>();
+ for (RcsTypeIdPair typeIdPair : mMessageTypeIdPairs) {
+ if (typeIdPair.getType() == MESSAGE_TYPE_INCOMING) {
+ messages.add(new RcsIncomingMessage(typeIdPair.getId()));
+ } else {
+ messages.add(new RcsOutgoingMessage(typeIdPair.getId()));
+ }
+ }
+
+ return messages;
+ }
+
+ protected RcsMessageQueryResult(Parcel in) {
+ mContinuationToken = in.readParcelable(
+ RcsQueryContinuationToken.class.getClassLoader());
+ in.readTypedList(mMessageTypeIdPairs, RcsTypeIdPair.CREATOR);
+ }
+
+ public static final Creator<RcsMessageQueryResult> CREATOR =
+ new Creator<RcsMessageQueryResult>() {
+ @Override
+ public RcsMessageQueryResult createFromParcel(Parcel in) {
+ return new RcsMessageQueryResult(in);
+ }
+
+ @Override
+ public RcsMessageQueryResult[] newArray(int size) {
+ return new RcsMessageQueryResult[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mContinuationToken, flags);
+ dest.writeTypedList(mMessageTypeIdPairs);
+ }
+}
diff --git a/telephony/java/android/telephony/ims/Rcs1To1Thread.aidl b/telephony/java/android/telephony/ims/RcsMessageSnippet.aidl
similarity index 95%
rename from telephony/java/android/telephony/ims/Rcs1To1Thread.aidl
rename to telephony/java/android/telephony/ims/RcsMessageSnippet.aidl
index 9fdc41d..99b8eb7 100644
--- a/telephony/java/android/telephony/ims/Rcs1To1Thread.aidl
+++ b/telephony/java/android/telephony/ims/RcsMessageSnippet.aidl
@@ -17,4 +17,4 @@
package android.telephony.ims;
-parcelable Rcs1To1Thread;
+parcelable RcsMessageSnippet;
diff --git a/telephony/java/android/telephony/ims/RcsMessageSnippet.java b/telephony/java/android/telephony/ims/RcsMessageSnippet.java
new file mode 100644
index 0000000..9399c20
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsMessageSnippet.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.ims;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.ims.RcsMessage.RcsMessageStatus;
+
+/**
+ * An immutable summary of the latest {@link RcsMessage} on an {@link RcsThread}
+ *
+ * @hide TODO: make public
+ */
+public class RcsMessageSnippet implements Parcelable {
+ private final String mText;
+ private final @RcsMessageStatus int mStatus;
+ private final long mTimestamp;
+
+ /**
+ * @hide
+ */
+ public RcsMessageSnippet(String text, @RcsMessageStatus int status, long timestamp) {
+ mText = text;
+ mStatus = status;
+ mTimestamp = timestamp;
+ }
+
+ /**
+ * @return Returns the text of the {@link RcsMessage} with highest origination timestamp value
+ * (i.e. latest) in this thread
+ */
+ @Nullable
+ public String getSnippetText() {
+ return mText;
+ }
+
+ /**
+ * @return Returns the status of the {@link RcsMessage} with highest origination timestamp value
+ * (i.e. latest) in this thread
+ */
+ public @RcsMessageStatus int getSnippetStatus() {
+ return mStatus;
+ }
+
+ /**
+ * @return Returns the timestamp of the {@link RcsMessage} with highest origination timestamp
+ * value (i.e. latest) in this thread
+ */
+ public long getSnippetTimestamp() {
+ return mTimestamp;
+ }
+
+ protected RcsMessageSnippet(Parcel in) {
+ mText = in.readString();
+ mStatus = in.readInt();
+ mTimestamp = in.readLong();
+ }
+
+ public static final Creator<RcsMessageSnippet> CREATOR =
+ new Creator<RcsMessageSnippet>() {
+ @Override
+ public RcsMessageSnippet createFromParcel(Parcel in) {
+ return new RcsMessageSnippet(in);
+ }
+
+ @Override
+ public RcsMessageSnippet[] newArray(int size) {
+ return new RcsMessageSnippet[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mText);
+ dest.writeInt(mStatus);
+ dest.writeLong(mTimestamp);
+ }
+}
diff --git a/telephony/java/android/telephony/ims/RcsMessageStore.java b/telephony/java/android/telephony/ims/RcsMessageStore.java
index 1bf6ffd..c8c36a8 100644
--- a/telephony/java/android/telephony/ims/RcsMessageStore.java
+++ b/telephony/java/android/telephony/ims/RcsMessageStore.java
@@ -16,106 +16,223 @@
package android.telephony.ims;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.WorkerThread;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.telephony.Rlog;
-import android.telephony.ims.aidl.IRcs;
+import android.net.Uri;
+
+import java.util.List;
/**
* RcsMessageStore is the application interface to RcsProvider and provides access methods to
* RCS related database tables.
+ *
* @hide - TODO make this public
*/
public class RcsMessageStore {
- static final String TAG = "RcsMessageStore";
-
/**
* Returns the first chunk of existing {@link RcsThread}s in the common storage.
+ *
* @param queryParameters Parameters to specify to return a subset of all RcsThreads.
* Passing a value of null will return all threads.
+ * @throws RcsMessageStoreException if the query could not be completed on the storage
*/
@WorkerThread
- public RcsThreadQueryResult getRcsThreads(@Nullable RcsThreadQueryParameters queryParameters) {
- try {
- IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs"));
- if (iRcs != null) {
- return iRcs.getRcsThreads(queryParameters);
- }
- } catch (RemoteException re) {
- Rlog.e(TAG, "RcsMessageStore: Exception happened during getRcsThreads", re);
- }
-
- return null;
+ @NonNull
+ public RcsThreadQueryResult getRcsThreads(@Nullable RcsThreadQueryParameters queryParameters)
+ throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getRcsThreads(queryParameters));
}
/**
* Returns the next chunk of {@link RcsThread}s in the common storage.
+ *
* @param continuationToken A token to continue the query to get the next chunk. This is
- * obtained through {@link RcsThreadQueryResult#nextChunkToken}.
+ * obtained through {@link RcsThreadQueryResult#getContinuationToken}.
+ * @throws RcsMessageStoreException if the query could not be completed on the storage
*/
@WorkerThread
- public RcsThreadQueryResult getRcsThreads(RcsThreadQueryContinuationToken continuationToken) {
- try {
- IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs"));
- if (iRcs != null) {
- return iRcs.getRcsThreadsWithToken(continuationToken);
- }
- } catch (RemoteException re) {
- Rlog.e(TAG, "RcsMessageStore: Exception happened during getRcsThreads", re);
- }
+ @NonNull
+ public RcsThreadQueryResult getRcsThreads(@NonNull RcsQueryContinuationToken continuationToken)
+ throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getRcsThreadsWithToken(continuationToken));
+ }
- return null;
+ /**
+ * Returns the first chunk of existing {@link RcsParticipant}s in the common storage.
+ *
+ * @param queryParameters Parameters to specify to return a subset of all RcsParticipants.
+ * Passing a value of null will return all participants.
+ * @throws RcsMessageStoreException if the query could not be completed on the storage
+ */
+ @WorkerThread
+ @NonNull
+ public RcsParticipantQueryResult getRcsParticipants(
+ @Nullable RcsParticipantQueryParameters queryParameters)
+ throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getParticipants(queryParameters));
+ }
+
+ /**
+ * Returns the next chunk of {@link RcsParticipant}s in the common storage.
+ *
+ * @param continuationToken A token to continue the query to get the next chunk. This is
+ * obtained through
+ * {@link RcsParticipantQueryResult#getContinuationToken}
+ * @throws RcsMessageStoreException if the query could not be completed on the storage
+ */
+ @WorkerThread
+ @NonNull
+ public RcsParticipantQueryResult getRcsParticipants(
+ @NonNull RcsQueryContinuationToken continuationToken)
+ throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getParticipantsWithToken(continuationToken));
+ }
+
+ /**
+ * Returns the first chunk of existing {@link RcsMessage}s in the common storage.
+ *
+ * @param queryParameters Parameters to specify to return a subset of all RcsMessages.
+ * Passing a value of null will return all messages.
+ * @throws RcsMessageStoreException if the query could not be completed on the storage
+ */
+ @WorkerThread
+ @NonNull
+ public RcsMessageQueryResult getRcsMessages(
+ @Nullable RcsMessageQueryParameters queryParameters) throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getMessages(queryParameters));
+ }
+
+ /**
+ * Returns the next chunk of {@link RcsMessage}s in the common storage.
+ *
+ * @param continuationToken A token to continue the query to get the next chunk. This is
+ * obtained through {@link RcsMessageQueryResult#getContinuationToken}
+ * @throws RcsMessageStoreException if the query could not be completed on the storage
+ */
+ @WorkerThread
+ @NonNull
+ public RcsMessageQueryResult getRcsMessages(
+ @NonNull RcsQueryContinuationToken continuationToken) throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getMessagesWithToken(continuationToken));
+ }
+
+ /**
+ * Returns the first chunk of existing {@link RcsEvent}s in the common storage.
+ *
+ * @param queryParameters Parameters to specify to return a subset of all RcsEvents.
+ * Passing a value of null will return all events.
+ * @throws RcsMessageStoreException if the query could not be completed on the storage
+ */
+ @WorkerThread
+ @NonNull
+ public RcsEventQueryResult getRcsEvents(
+ @Nullable RcsEventQueryParameters queryParameters) throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getEvents(queryParameters));
+ }
+
+ /**
+ * Returns the next chunk of {@link RcsEvent}s in the common storage.
+ *
+ * @param continuationToken A token to continue the query to get the next chunk. This is
+ * obtained through {@link RcsEventQueryResult#getContinuationToken}.
+ * @throws RcsMessageStoreException if the query could not be completed on the storage
+ */
+ @WorkerThread
+ @NonNull
+ public RcsEventQueryResult getRcsEvents(
+ @NonNull RcsQueryContinuationToken continuationToken) throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getEventsWithToken(continuationToken));
+ }
+
+ /**
+ * Persists an {@link RcsEvent} to common storage.
+ *
+ * @param persistableEvent The {@link RcsEvent} to persist into storage.
+ * @throws RcsMessageStoreException if the query could not be completed on the storage
+ *
+ * @see RcsGroupThreadNameChangedEvent
+ * @see RcsGroupThreadIconChangedEvent
+ * @see RcsGroupThreadParticipantJoinedEvent
+ * @see RcsGroupThreadParticipantLeftEvent
+ * @see RcsParticipantAliasChangedEvent
+ */
+ @WorkerThread
+ @NonNull
+ public void persistRcsEvent(RcsEvent persistableEvent) throws RcsMessageStoreException {
+ persistableEvent.persist();
}
/**
* Creates a new 1 to 1 thread with the given participant and persists it in the storage.
+ *
+ * @param recipient The {@link RcsParticipant} that will receive the messages in this thread.
+ * @return The newly created {@link Rcs1To1Thread}
+ * @throws RcsMessageStoreException if the thread could not be persisted in the storage
*/
@WorkerThread
- public Rcs1To1Thread createRcs1To1Thread(RcsParticipant recipient) {
- try {
- IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs"));
- if (iRcs != null) {
- return iRcs.createRcs1To1Thread(recipient);
- }
- } catch (RemoteException re) {
- Rlog.e(TAG, "RcsMessageStore: Exception happened during createRcs1To1Thread", re);
- }
-
- return null;
+ @NonNull
+ public Rcs1To1Thread createRcs1To1Thread(@NonNull RcsParticipant recipient)
+ throws RcsMessageStoreException {
+ return new Rcs1To1Thread(
+ RcsControllerCall.call(iRcs -> iRcs.createRcs1To1Thread(recipient.getId())));
}
/**
- * Delete the {@link RcsThread} identified by the given threadId.
- * @param threadId threadId of the thread to be deleted.
+ * Creates a new group thread with the given participants and persists it in the storage.
+ *
+ * @throws RcsMessageStoreException if the thread could not be persisted in the storage
*/
@WorkerThread
- public void deleteThread(int threadId) {
- try {
- IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs"));
- if (iRcs != null) {
- iRcs.deleteThread(threadId);
+ @NonNull
+ public RcsGroupThread createGroupThread(@Nullable List<RcsParticipant> recipients,
+ @Nullable String groupName, @Nullable Uri groupIcon) throws RcsMessageStoreException {
+ int[] recipientIds = null;
+ if (recipients != null) {
+ recipientIds = new int[recipients.size()];
+
+ for (int i = 0; i < recipients.size(); i++) {
+ recipientIds[i] = recipients.get(i).getId();
}
- } catch (RemoteException re) {
- Rlog.e(TAG, "RcsMessageStore: Exception happened during deleteThread", re);
+ }
+
+ int[] finalRecipientIds = recipientIds;
+ return new RcsGroupThread(RcsControllerCall.call(
+ iRcs -> iRcs.createGroupThread(finalRecipientIds, groupName, groupIcon)));
+ }
+
+ /**
+ * Delete the given {@link RcsThread} from the storage.
+ *
+ * @param thread The thread to be deleted.
+ * @throws RcsMessageStoreException if the thread could not be deleted from the storage
+ */
+ @WorkerThread
+ public void deleteThread(@NonNull RcsThread thread) throws RcsMessageStoreException {
+ if (thread == null) {
+ return;
+ }
+
+ boolean isDeleteSucceeded = RcsControllerCall.call(
+ iRcs -> iRcs.deleteThread(thread.getThreadId(), thread.getThreadType()));
+
+ if (!isDeleteSucceeded) {
+ throw new RcsMessageStoreException("Could not delete RcsThread");
}
}
/**
* Creates a new participant and persists it in the storage.
+ *
* @param canonicalAddress The defining address (e.g. phone number) of the participant.
+ * @param alias The RCS alias for the participant.
+ * @throws RcsMessageStoreException if the participant could not be created on the storage
*/
- public RcsParticipant createRcsParticipant(String canonicalAddress) {
- try {
- IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs"));
- if (iRcs != null) {
- return iRcs.createRcsParticipant(canonicalAddress);
- }
- } catch (RemoteException re) {
- Rlog.e(TAG, "RcsMessageStore: Exception happened during createRcsParticipant", re);
- }
-
- return null;
+ @WorkerThread
+ @NonNull
+ public RcsParticipant createRcsParticipant(String canonicalAddress, @Nullable String alias)
+ throws RcsMessageStoreException {
+ return new RcsParticipant(
+ RcsControllerCall.call(iRcs -> iRcs.createRcsParticipant(canonicalAddress, alias)));
}
}
diff --git a/telephony/java/android/telephony/ims/RcsMessageStoreException.java b/telephony/java/android/telephony/ims/RcsMessageStoreException.java
new file mode 100644
index 0000000..e158f1a
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsMessageStoreException.java
@@ -0,0 +1,35 @@
+/*
+ * 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.telephony.ims;
+
+/**
+ * An exception that happened on {@link RcsMessageStore} or one of the derived storage classes in
+ * {@link android.telephony.ims}
+ *
+ * @hide TODO: make public
+ */
+public class RcsMessageStoreException extends Exception {
+
+ /**
+ * Constructs an {@link RcsMessageStoreException} with the specified detail message.
+ * @param message The detail message
+ * @see Throwable#getMessage()
+ */
+ public RcsMessageStoreException(String message) {
+ super(message);
+ }
+}
diff --git a/telephony/java/android/telephony/ims/RcsMultiMediaPart.java b/telephony/java/android/telephony/ims/RcsMultiMediaPart.java
deleted file mode 100644
index d295fba..0000000
--- a/telephony/java/android/telephony/ims/RcsMultiMediaPart.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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.telephony.ims;
-
-import android.os.Parcel;
-
-/**
- * A part of a composite {@link RcsMessage} that holds a media that is rendered on the screen
- * (i.e. image, video etc)
- * @hide - TODO(sahinc) make this public
- */
-public class RcsMultiMediaPart extends RcsFileTransferPart {
- public static final Creator<RcsMultiMediaPart> CREATOR = new Creator<RcsMultiMediaPart>() {
- @Override
- public RcsMultiMediaPart createFromParcel(Parcel in) {
- return new RcsMultiMediaPart(in);
- }
-
- @Override
- public RcsMultiMediaPart[] newArray(int size) {
- return new RcsMultiMediaPart[size];
- }
- };
-
- protected RcsMultiMediaPart(Parcel in) {
- super(in);
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- }
-}
diff --git a/telephony/java/android/telephony/ims/RcsMultimediaPart.aidl b/telephony/java/android/telephony/ims/RcsMultimediaPart.aidl
deleted file mode 100644
index 5992d95..0000000
--- a/telephony/java/android/telephony/ims/RcsMultimediaPart.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- *
- * Copyright 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.telephony.ims;
-
-parcelable RcsMultimediaPart;
diff --git a/telephony/java/android/telephony/ims/RcsOutgoingMessage.aidl b/telephony/java/android/telephony/ims/RcsOutgoingMessage.aidl
deleted file mode 100644
index 6e0c80f..0000000
--- a/telephony/java/android/telephony/ims/RcsOutgoingMessage.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- *
- * Copyright 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.telephony.ims;
-
-parcelable RcsOutgoingMessage;
diff --git a/telephony/java/android/telephony/ims/RcsOutgoingMessage.java b/telephony/java/android/telephony/ims/RcsOutgoingMessage.java
index bfb1611..0bd55ec 100644
--- a/telephony/java/android/telephony/ims/RcsOutgoingMessage.java
+++ b/telephony/java/android/telephony/ims/RcsOutgoingMessage.java
@@ -15,34 +15,53 @@
*/
package android.telephony.ims;
-import android.os.Parcel;
+import android.annotation.NonNull;
+import android.annotation.WorkerThread;
+
+import java.util.ArrayList;
+import java.util.List;
/**
* This is a single instance of a message sent over RCS.
- * @hide - TODO(sahinc) make this public
+ *
+ * @hide - TODO(109759350) make this public
*/
public class RcsOutgoingMessage extends RcsMessage {
- public static final Creator<RcsOutgoingMessage> CREATOR = new Creator<RcsOutgoingMessage>() {
- @Override
- public RcsOutgoingMessage createFromParcel(Parcel in) {
- return new RcsOutgoingMessage(in);
- }
-
- @Override
- public RcsOutgoingMessage[] newArray(int size) {
- return new RcsOutgoingMessage[size];
- }
- };
-
- protected RcsOutgoingMessage(Parcel in) {
+ RcsOutgoingMessage(int id) {
+ super(id);
}
- @Override
- public int describeContents() {
- return 0;
+ /**
+ * @return Returns the {@link RcsOutgoingMessageDelivery}s associated with this message. Please
+ * note that the deliveries returned for the {@link RcsOutgoingMessage} may not always match the
+ * {@link RcsParticipant}s on the {@link RcsGroupThread} as the group recipients may have
+ * changed.
+ * @throws RcsMessageStoreException if the outgoing deliveries could not be read from storage.
+ */
+ @NonNull
+ @WorkerThread
+ public List<RcsOutgoingMessageDelivery> getOutgoingDeliveries()
+ throws RcsMessageStoreException {
+ int[] deliveryParticipants;
+ List<RcsOutgoingMessageDelivery> messageDeliveries = new ArrayList<>();
+
+ deliveryParticipants = RcsControllerCall.call(
+ iRcs -> iRcs.getMessageRecipients(mId));
+
+ if (deliveryParticipants != null) {
+ for (Integer deliveryParticipant : deliveryParticipants) {
+ messageDeliveries.add(new RcsOutgoingMessageDelivery(deliveryParticipant, mId));
+ }
+ }
+
+ return messageDeliveries;
}
+ /**
+ * @return Returns {@code false} as this is not an incoming message.
+ */
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public boolean isIncoming() {
+ return false;
}
}
diff --git a/telephony/java/android/telephony/ims/RcsOutgoingMessageDelivery.java b/telephony/java/android/telephony/ims/RcsOutgoingMessageDelivery.java
new file mode 100644
index 0000000..b93f892
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsOutgoingMessageDelivery.java
@@ -0,0 +1,131 @@
+/*
+ * 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.telephony.ims;
+
+import android.annotation.NonNull;
+import android.annotation.WorkerThread;
+
+/**
+ * This class holds the delivery information of an {@link RcsOutgoingMessage} for each
+ * {@link RcsParticipant} that the message was intended for.
+ *
+ * @hide - TODO(109759350) make this public
+ */
+public class RcsOutgoingMessageDelivery {
+ // The participant that this delivery is intended for
+ private final int mRecipientId;
+ // The message this delivery is associated with
+ private final int mRcsOutgoingMessageId;
+
+ /**
+ * Constructor to be used with RcsOutgoingMessage.getDelivery()
+ *
+ * @hide
+ */
+ RcsOutgoingMessageDelivery(int recipientId, int messageId) {
+ mRecipientId = recipientId;
+ mRcsOutgoingMessageId = messageId;
+ }
+
+ /**
+ * Sets the delivery time of this outgoing delivery and persists into storage.
+ *
+ * @param deliveredTimestamp The timestamp to set to delivery. It is defined as milliseconds
+ * passed after midnight, January 1, 1970 UTC
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setDeliveredTimestamp(long deliveredTimestamp) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setOutgoingDeliveryDeliveredTimestamp(
+ mRcsOutgoingMessageId, mRecipientId, deliveredTimestamp));
+ }
+
+ /**
+ * @return Returns the delivered timestamp of the associated message to the associated
+ * participant. Timestamp is defined as milliseconds passed after midnight, January 1, 1970 UTC.
+ * Returns 0 if the {@link RcsOutgoingMessage} is not delivered yet.
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @WorkerThread
+ public long getDeliveredTimestamp() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getOutgoingDeliveryDeliveredTimestamp(
+ mRcsOutgoingMessageId, mRecipientId));
+ }
+
+ /**
+ * Sets the seen time of this outgoing delivery and persists into storage.
+ *
+ * @param seenTimestamp The timestamp to set to delivery. It is defined as milliseconds
+ * passed after midnight, January 1, 1970 UTC
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setSeenTimestamp(long seenTimestamp) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setOutgoingDeliverySeenTimestamp(
+ mRcsOutgoingMessageId, mRecipientId, seenTimestamp));
+ }
+
+ /**
+ * @return Returns the seen timestamp of the associated message by the associated
+ * participant. Timestamp is defined as milliseconds passed after midnight, January 1, 1970 UTC.
+ * Returns 0 if the {@link RcsOutgoingMessage} is not seen yet.
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @WorkerThread
+ public long getSeenTimestamp() throws RcsMessageStoreException {
+ return RcsControllerCall.call(
+ iRcs -> iRcs.getOutgoingDeliverySeenTimestamp(mRcsOutgoingMessageId, mRecipientId));
+ }
+
+ /**
+ * Sets the status of this outgoing delivery and persists into storage.
+ *
+ * @param status The status of the associated {@link RcsMessage}s delivery to the associated
+ * {@link RcsParticipant}
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
+ */
+ @WorkerThread
+ public void setStatus(@RcsMessage.RcsMessageStatus int status) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setOutgoingDeliveryStatus(
+ mRcsOutgoingMessageId, mRecipientId, status));
+ }
+
+ /**
+ * @return Returns the status of this outgoing delivery.
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @WorkerThread
+ public @RcsMessage.RcsMessageStatus int getStatus() throws RcsMessageStoreException {
+ return RcsControllerCall.call(
+ iRcs -> iRcs.getOutgoingDeliveryStatus(mRcsOutgoingMessageId, mRecipientId));
+ }
+
+ /**
+ * @return Returns the recipient associated with this delivery.
+ */
+ @NonNull
+ public RcsParticipant getRecipient() {
+ return new RcsParticipant(mRecipientId);
+ }
+
+ /**
+ * @return Returns the {@link RcsOutgoingMessage} associated with this delivery.
+ */
+ @NonNull
+ public RcsOutgoingMessage getMessage() {
+ return new RcsOutgoingMessage(mRcsOutgoingMessageId);
+ }
+}
diff --git a/telephony/java/android/telephony/ims/RcsParticipant.aidl b/telephony/java/android/telephony/ims/RcsParticipant.aidl
deleted file mode 100644
index 1c44363..0000000
--- a/telephony/java/android/telephony/ims/RcsParticipant.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- *
- * Copyright 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.telephony.ims;
-
-parcelable RcsParticipant;
diff --git a/telephony/java/android/telephony/ims/RcsParticipant.java b/telephony/java/android/telephony/ims/RcsParticipant.java
index f678ec7..ce0ad2c 100644
--- a/telephony/java/android/telephony/ims/RcsParticipant.java
+++ b/telephony/java/android/telephony/ims/RcsParticipant.java
@@ -15,33 +15,17 @@
*/
package android.telephony.ims;
-import static android.telephony.ims.RcsMessageStore.TAG;
-
-import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.WorkerThread;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.telephony.Rlog;
-import android.telephony.ims.aidl.IRcs;
-import android.text.TextUtils;
-
-import com.android.internal.util.Preconditions;
/**
* RcsParticipant is an RCS capable contact that can participate in {@link RcsThread}s.
- * @hide - TODO(sahinc) make this public
+ *
+ * @hide - TODO(109759350) make this public
*/
-public class RcsParticipant implements Parcelable {
+public class RcsParticipant {
// The row ID of this participant in the database
private int mId;
- // The phone number of this participant
- private String mCanonicalAddress;
- // The RCS alias of this participant. This is different than the name of the contact in the
- // Contacts app - i.e. RCS protocol allows users to define aliases for themselves that doesn't
- // require other users to add them as contacts and give them a name.
- private String mAlias;
/**
* Constructor for {@link com.android.internal.telephony.ims.RcsMessageStoreController}
@@ -49,68 +33,87 @@
*
* @hide
*/
- public RcsParticipant(int id, @NonNull String canonicalAddress) {
+ public RcsParticipant(int id) {
mId = id;
- mCanonicalAddress = canonicalAddress;
}
/**
- * @return Returns the canonical address (i.e. normalized phone number) for this participant
+ * @return Returns the canonical address (i.e. normalized phone number) for this
+ * {@link RcsParticipant}
+ * @throws RcsMessageStoreException if the value could not be read from the storage
*/
- public String getCanonicalAddress() {
- return mCanonicalAddress;
+ @Nullable
+ @WorkerThread
+ public String getCanonicalAddress() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getRcsParticipantCanonicalAddress(mId));
}
/**
- * Sets the canonical address for this participant and updates it in storage.
- * @param canonicalAddress the canonical address to update to.
+ * @return Returns the alias for this {@link RcsParticipant}. Alias is usually the real name of
+ * the person themselves. Please see US5-15 - GSMA RCC.71 (RCS Universal Profile Service
+ * Definition Document)
+ * @throws RcsMessageStoreException if the value could not be read from the storage
+ */
+ @Nullable
+ @WorkerThread
+ public String getAlias() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getRcsParticipantAlias(mId));
+ }
+
+ /**
+ * Sets the alias for this {@link RcsParticipant} and persists it in storage. Alias is usually
+ * the real name of the person themselves. Please see US5-15 - GSMA RCC.71 (RCS Universal
+ * Profile Service Definition Document)
+ *
+ * @param alias The alias to set to.
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
*/
@WorkerThread
- public void setCanonicalAddress(@NonNull String canonicalAddress) {
- Preconditions.checkNotNull(canonicalAddress);
- if (canonicalAddress.equals(mCanonicalAddress)) {
- return;
- }
-
- mCanonicalAddress = canonicalAddress;
-
- try {
- IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs"));
- if (iRcs != null) {
- iRcs.updateRcsParticipantCanonicalAddress(mId, mCanonicalAddress);
- }
- } catch (RemoteException re) {
- Rlog.e(TAG, "RcsParticipant: Exception happened during setCanonicalAddress", re);
- }
+ public void setAlias(String alias) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setRcsParticipantAlias(mId, alias));
}
/**
- * @return Returns the alias for this participant. Alias is usually the real name of the person
- * themselves.
+ * @return Returns the contact ID for this {@link RcsParticipant}. Contact ID is a unique ID for
+ * an {@link RcsParticipant} that is RCS provisioned. Please see 4.4.5 - GSMA RCC.53 (RCS Device
+ * API 1.6 Specification)
+ * @throws RcsMessageStoreException if the value could not be read from the storage
*/
- public String getAlias() {
- return mAlias;
+ @Nullable
+ @WorkerThread
+ public String getContactId() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getRcsParticipantContactId(mId));
}
/**
- * Sets the alias for this participant and persists it in storage. Alias is usually the real
- * name of the person themselves.
+ * Sets the contact ID for this {@link RcsParticipant}. Contact ID is a unique ID for
+ * an {@link RcsParticipant} that is RCS provisioned. Please see 4.4.5 - GSMA RCC.53 (RCS Device
+ * API 1.6 Specification)
+ *
+ * @param contactId The contact ID to set to.
+ * @throws RcsMessageStoreException if the value could not be persisted into storage
*/
@WorkerThread
- public void setAlias(String alias) {
- if (TextUtils.equals(mAlias, alias)) {
- return;
- }
- mAlias = alias;
+ public void setContactId(String contactId) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(iRcs -> iRcs.setRcsParticipantContactId(mId, contactId));
+ }
- try {
- IRcs iRcs = IRcs.Stub.asInterface(ServiceManager.getService("ircs"));
- if (iRcs != null) {
- iRcs.updateRcsParticipantAlias(mId, mAlias);
- }
- } catch (RemoteException re) {
- Rlog.e(TAG, "RcsParticipant: Exception happened during setCanonicalAddress", re);
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
}
+ if (!(obj instanceof RcsParticipant)) {
+ return false;
+ }
+ RcsParticipant other = (RcsParticipant) obj;
+
+ return mId == other.mId;
+ }
+
+ @Override
+ public int hashCode() {
+ return mId;
}
/**
@@ -121,34 +124,4 @@
public int getId() {
return mId;
}
-
- public static final Creator<RcsParticipant> CREATOR = new Creator<RcsParticipant>() {
- @Override
- public RcsParticipant createFromParcel(Parcel in) {
- return new RcsParticipant(in);
- }
-
- @Override
- public RcsParticipant[] newArray(int size) {
- return new RcsParticipant[size];
- }
- };
-
- protected RcsParticipant(Parcel in) {
- mId = in.readInt();
- mCanonicalAddress = in.readString();
- mAlias = in.readString();
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mId);
- dest.writeString(mCanonicalAddress);
- dest.writeString(mAlias);
- }
}
diff --git a/telephony/java/android/telephony/ims/RcsParticipantAliasChangedEvent.java b/telephony/java/android/telephony/ims/RcsParticipantAliasChangedEvent.java
index b9ca5a8..04cdf86 100644
--- a/telephony/java/android/telephony/ims/RcsParticipantAliasChangedEvent.java
+++ b/telephony/java/android/telephony/ims/RcsParticipantAliasChangedEvent.java
@@ -15,27 +15,93 @@
*/
package android.telephony.ims;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
/**
- * An event that indicates an {@link RcsParticipant}'s alias was changed.
- * @hide - TODO(sahinc) make this public
+ * An event that indicates an {@link RcsParticipant}'s alias was changed. Please see US18-2 - GSMA
+ * RCC.71 (RCS Universal Profile Service Definition Document)
+ *
+ * @hide - TODO(109759350) make this public
*/
-public class RcsParticipantAliasChangedEvent extends RcsParticipantEvent {
+public class RcsParticipantAliasChangedEvent extends RcsEvent {
+ // The ID of the participant that changed their alias
+ private int mParticipantId;
+ // The new alias of the above participant
+ private String mNewAlias;
+
+ /**
+ * Creates a new {@link RcsParticipantAliasChangedEvent}. This event is not persisted into
+ * storage until {@link RcsMessageStore#persistRcsEvent(RcsEvent)} is called.
+ *
+ * @param timestamp The timestamp of when this event happened, in milliseconds passed after
+ * midnight, January 1st, 1970 UTC
+ * @param participant The {@link RcsParticipant} that got their alias changed
+ * @param newAlias The new alias the {@link RcsParticipant} has.
+ * @see RcsMessageStore#persistRcsEvent(RcsEvent)
+ */
+ public RcsParticipantAliasChangedEvent(long timestamp, @NonNull RcsParticipant participant,
+ @Nullable String newAlias) {
+ super(timestamp);
+ mParticipantId = participant.getId();
+ mNewAlias = newAlias;
+ }
+
+ /**
+ * @hide - internal constructor for queries
+ */
+ public RcsParticipantAliasChangedEvent(long timestamp, int participantId,
+ @Nullable String newAlias) {
+ super(timestamp);
+ mParticipantId = participantId;
+ mNewAlias = newAlias;
+ }
+
+ /**
+ * @return Returns the {@link RcsParticipant} whose alias was changed.
+ */
+ @NonNull
+ public RcsParticipant getParticipantId() {
+ return new RcsParticipant(mParticipantId);
+ }
+
+ /**
+ * @return Returns the alias of the associated {@link RcsParticipant} after this event happened
+ */
+ @Nullable
+ public String getNewAlias() {
+ return mNewAlias;
+ }
+
+ /**
+ * Persists the event to the data store.
+ *
+ * @hide - not meant for public use.
+ */
+ @Override
+ public void persist() throws RcsMessageStoreException {
+ RcsControllerCall.call(iRcs -> iRcs.createParticipantAliasChangedEvent(
+ getTimestamp(), getParticipantId().getId(), getNewAlias()));
+ }
+
public static final Creator<RcsParticipantAliasChangedEvent> CREATOR =
new Creator<RcsParticipantAliasChangedEvent>() {
- @Override
- public RcsParticipantAliasChangedEvent createFromParcel(Parcel in) {
- return new RcsParticipantAliasChangedEvent(in);
- }
+ @Override
+ public RcsParticipantAliasChangedEvent createFromParcel(Parcel in) {
+ return new RcsParticipantAliasChangedEvent(in);
+ }
- @Override
- public RcsParticipantAliasChangedEvent[] newArray(int size) {
- return new RcsParticipantAliasChangedEvent[size];
- }
- };
+ @Override
+ public RcsParticipantAliasChangedEvent[] newArray(int size) {
+ return new RcsParticipantAliasChangedEvent[size];
+ }
+ };
protected RcsParticipantAliasChangedEvent(Parcel in) {
+ super(in);
+ mNewAlias = in.readString();
+ mParticipantId = in.readInt();
}
@Override
@@ -45,5 +111,8 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeString(mNewAlias);
+ dest.writeInt(mParticipantId);
}
}
diff --git a/telephony/java/android/telephony/ims/RcsParticipantEvent.aidl b/telephony/java/android/telephony/ims/RcsParticipantEvent.aidl
deleted file mode 100644
index c0a7789..0000000
--- a/telephony/java/android/telephony/ims/RcsParticipantEvent.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- *
- * Copyright 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.telephony.ims;
-
-parcelable RcsParticipantEvent;
diff --git a/telephony/java/android/telephony/ims/RcsParticipantEvent.java b/telephony/java/android/telephony/ims/RcsParticipantEvent.java
deleted file mode 100644
index 371b8b7..0000000
--- a/telephony/java/android/telephony/ims/RcsParticipantEvent.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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.telephony.ims;
-
-import android.os.Parcelable;
-
-/**
- * An event that is associated with an {@link RcsParticipant}
- * @hide - TODO(sahinc) make this public
- */
-public abstract class RcsParticipantEvent implements Parcelable {
-}
diff --git a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl b/telephony/java/android/telephony/ims/RcsParticipantQueryParameters.aidl
similarity index 93%
copy from telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
copy to telephony/java/android/telephony/ims/RcsParticipantQueryParameters.aidl
index 82d985d..ea8288c 100644
--- a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
+++ b/telephony/java/android/telephony/ims/RcsParticipantQueryParameters.aidl
@@ -17,4 +17,4 @@
package android.telephony.ims;
-parcelable RcsThreadIconChangedEvent;
+parcelable RcsParticipantQueryParameters;
diff --git a/telephony/java/android/telephony/ims/RcsParticipantQueryParameters.java b/telephony/java/android/telephony/ims/RcsParticipantQueryParameters.java
new file mode 100644
index 0000000..3611fc1
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsParticipantQueryParameters.java
@@ -0,0 +1,310 @@
+/*
+ * 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.telephony.ims;
+
+import android.annotation.CheckResult;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.security.InvalidParameterException;
+
+/**
+ * The parameters to pass into
+ * {@link RcsMessageStore#getRcsParticipants(RcsParticipantQueryParameters)} in order to select a
+ * subset of {@link RcsThread}s present in the message store.
+ *
+ * @hide TODO - make the Builder and builder() public. The rest should stay internal only.
+ */
+public class RcsParticipantQueryParameters implements Parcelable {
+ /**
+ * Flag to set with {@link Builder#setSortProperty(int)} to sort the results in the order of
+ * creation time for faster query results
+ */
+ public static final int SORT_BY_CREATION_ORDER = 0;
+
+ /**
+ * Flag to set with {@link Builder#setSortProperty(int)} to sort depending on the
+ * {@link RcsParticipant} aliases
+ */
+ public static final int SORT_BY_ALIAS = 1;
+
+ /**
+ * Flag to set with {@link Builder#setSortProperty(int)} to sort depending on the
+ * {@link RcsParticipant} canonical addresses
+ */
+ public static final int SORT_BY_CANONICAL_ADDRESS = 2;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SORT_BY_CREATION_ORDER, SORT_BY_ALIAS, SORT_BY_CANONICAL_ADDRESS})
+ public @interface SortingProperty {
+ }
+
+ // The SQL "like" statement to filter against participant aliases
+ private String mAliasLike;
+ // The SQL "like" statement to filter against canonical addresses
+ private String mCanonicalAddressLike;
+ // The property to sort the result against
+ private @SortingProperty int mSortingProperty;
+ // Whether to sort the result in ascending order
+ private boolean mIsAscending;
+ // The number of results to be returned from the query
+ private int mLimit;
+ // Used to limit the results to participants of a single thread
+ private int mThreadId;
+
+ /**
+ * @hide
+ */
+ public static final String PARTICIPANT_QUERY_PARAMETERS_KEY = "participant_query_parameters";
+
+ RcsParticipantQueryParameters(int rcsThreadId, String aliasLike, String canonicalAddressLike,
+ @SortingProperty int sortingProperty, boolean isAscending,
+ int limit) {
+ mThreadId = rcsThreadId;
+ mAliasLike = aliasLike;
+ mCanonicalAddressLike = canonicalAddressLike;
+ mSortingProperty = sortingProperty;
+ mIsAscending = isAscending;
+ mLimit = limit;
+ }
+
+ /**
+ * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to get
+ * the thread that the result query should be limited to.
+ *
+ * As we do not expose any sort of integer ID's to public usage, this should be hidden.
+ *
+ * @hide - not meant for public use
+ */
+ public int getThreadId() {
+ return mThreadId;
+ }
+
+ /**
+ * @return Returns the SQL-inspired "LIKE" clause that will be used to match
+ * {@link RcsParticipant}s with respect to their aliases
+ *
+ * @see RcsParticipant#getAlias()
+ */
+ public String getAliasLike() {
+ return mAliasLike;
+ }
+
+ /**
+ * @return Returns the SQL-inspired "LIKE" clause that will be used to match
+ * {@link RcsParticipant}s with respect to their canonical addresses.
+ *
+ * @see RcsParticipant#getCanonicalAddress()
+ */
+ public String getCanonicalAddressLike() {
+ return mCanonicalAddressLike;
+ }
+
+ /**
+ * @return Returns the number of {@link RcsParticipant}s to be returned from the query. A value
+ * of 0 means there is no set limit.
+ */
+ public int getLimit() {
+ return mLimit;
+ }
+
+ /**
+ * @return Returns the property that will be used to sort the result against.
+ * @see SortingProperty
+ */
+ public int getSortingProperty() {
+ return mSortingProperty;
+ }
+
+ /**
+ * @return Returns {@code true} if the result set will be sorted in ascending order,
+ * {@code false} if it will be sorted in descending order.
+ */
+ public boolean getSortDirection() {
+ return mIsAscending;
+ }
+
+ /**
+ * A helper class to build the {@link RcsParticipantQueryParameters}.
+ */
+ public static class Builder {
+ private String mAliasLike;
+ private String mCanonicalAddressLike;
+ private @SortingProperty int mSortingProperty;
+ private boolean mIsAscending;
+ private int mLimit = 100;
+ private int mThreadId;
+
+ /**
+ * Creates a new builder for {@link RcsParticipantQueryParameters} to be used in
+ * {@link RcsMessageStore#getRcsParticipants(RcsParticipantQueryParameters)}
+ */
+ public Builder() {
+ // empty implementation
+ }
+
+ /**
+ * Limits the resulting {@link RcsParticipant}s to only the given {@link RcsThread}
+ *
+ * @param rcsThread The thread that the participants should be searched in.
+ * @return The same {@link Builder} to chain methods.
+ */
+ @CheckResult
+ public Builder setThread(RcsThread rcsThread) {
+ mThreadId = rcsThread.getThreadId();
+ return this;
+ }
+
+ /**
+ * Sets an SQL-inspired "like" clause to match with participant aliases. Using a percent
+ * sign ('%') wildcard matches any sequence of zero or more characters. Using an underscore
+ * ('_') wildcard matches any single character. Not using any wildcards would only perform a
+ * string match.The input string is case-insensitive.
+ *
+ * The input "An%e" would match {@link RcsParticipant}s with names Anne, Annie, Antonie,
+ * while the input "An_e" would only match Anne.
+ *
+ * @param likeClause The like clause to use for matching {@link RcsParticipant} aliases.
+ * @return The same {@link Builder} to chain methods
+ */
+ @CheckResult
+ public Builder setAliasLike(String likeClause) {
+ mAliasLike = likeClause;
+ return this;
+ }
+
+ /**
+ * Sets an SQL-inspired "like" clause to match with participant addresses. Using a percent
+ * sign ('%') wildcard matches any sequence of zero or more characters. Using an underscore
+ * ('_') wildcard matches any single character. Not using any wildcards would only perform a
+ * string match. The input string is case-insensitive.
+ *
+ * The input "+999%111" would match {@link RcsParticipant}s with addresses like "+9995111"
+ * or "+99955555111", while the input "+999_111" would only match "+9995111".
+ *
+ * @param likeClause The like clause to use for matching {@link RcsParticipant} canonical
+ * addresses.
+ * @return The same {@link Builder} to chain methods
+ */
+ @CheckResult
+ public Builder setCanonicalAddressLike(String likeClause) {
+ mCanonicalAddressLike = likeClause;
+ return this;
+ }
+
+ /**
+ * Desired number of threads to be returned from the query. Passing in 0 will return all
+ * existing threads at once. The limit defaults to 100.
+ *
+ * @param limit The number to limit the query result to.
+ * @return The same instance of the builder to chain parameters.
+ * @throws InvalidParameterException If the given limit is negative.
+ */
+ @CheckResult
+ public Builder setResultLimit(@IntRange(from = 0) int limit)
+ throws InvalidParameterException {
+ if (limit < 0) {
+ throw new InvalidParameterException("The query limit must be non-negative");
+ }
+
+ mLimit = limit;
+ return this;
+ }
+
+ /**
+ * Sets the property where the results should be sorted against. Defaults to
+ * {@link RcsParticipantQueryParameters.SortingProperty#SORT_BY_CREATION_ORDER}
+ *
+ * @param sortingProperty against which property the results should be sorted
+ * @return The same instance of the builder to chain parameters.
+ */
+ @CheckResult
+ public Builder setSortProperty(@SortingProperty int sortingProperty) {
+ mSortingProperty = sortingProperty;
+ return this;
+ }
+
+ /**
+ * Sets whether the results should be sorted ascending or descending
+ *
+ * @param isAscending whether the results should be sorted ascending
+ * @return The same instance of the builder to chain parameters.
+ */
+ @CheckResult
+ public Builder setSortDirection(boolean isAscending) {
+ mIsAscending = isAscending;
+ return this;
+ }
+
+ /**
+ * Builds the {@link RcsParticipantQueryParameters} to use in
+ * {@link RcsMessageStore#getRcsParticipants(RcsParticipantQueryParameters)}
+ *
+ * @return An instance of {@link RcsParticipantQueryParameters} to use with the participant
+ * query.
+ */
+ public RcsParticipantQueryParameters build() {
+ return new RcsParticipantQueryParameters(mThreadId, mAliasLike, mCanonicalAddressLike,
+ mSortingProperty, mIsAscending, mLimit);
+ }
+ }
+
+ /**
+ * Parcelable boilerplate below.
+ */
+ protected RcsParticipantQueryParameters(Parcel in) {
+ mAliasLike = in.readString();
+ mCanonicalAddressLike = in.readString();
+ mSortingProperty = in.readInt();
+ mIsAscending = in.readByte() == 1;
+ mLimit = in.readInt();
+ mThreadId = in.readInt();
+ }
+
+ public static final Creator<RcsParticipantQueryParameters> CREATOR =
+ new Creator<RcsParticipantQueryParameters>() {
+ @Override
+ public RcsParticipantQueryParameters createFromParcel(Parcel in) {
+ return new RcsParticipantQueryParameters(in);
+ }
+
+ @Override
+ public RcsParticipantQueryParameters[] newArray(int size) {
+ return new RcsParticipantQueryParameters[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mAliasLike);
+ dest.writeString(mCanonicalAddressLike);
+ dest.writeInt(mSortingProperty);
+ dest.writeByte((byte) (mIsAscending ? 1 : 0));
+ dest.writeInt(mLimit);
+ dest.writeInt(mThreadId);
+ }
+
+}
diff --git a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl b/telephony/java/android/telephony/ims/RcsParticipantQueryResult.aidl
similarity index 94%
copy from telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
copy to telephony/java/android/telephony/ims/RcsParticipantQueryResult.aidl
index 82d985d..db5c00c 100644
--- a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
+++ b/telephony/java/android/telephony/ims/RcsParticipantQueryResult.aidl
@@ -17,4 +17,4 @@
package android.telephony.ims;
-parcelable RcsThreadIconChangedEvent;
+parcelable RcsParticipantQueryResult;
diff --git a/telephony/java/android/telephony/ims/RcsParticipantQueryResult.java b/telephony/java/android/telephony/ims/RcsParticipantQueryResult.java
new file mode 100644
index 0000000..2f4ab46
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsParticipantQueryResult.java
@@ -0,0 +1,105 @@
+/*
+ * 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.telephony.ims;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The result of a {@link RcsMessageStore#getRcsParticipants(RcsParticipantQueryParameters)}
+ * call. This class allows getting the token for querying the next batch of participants in order to
+ * prevent handling large amounts of data at once.
+ *
+ * @hide
+ */
+public class RcsParticipantQueryResult implements Parcelable {
+ // A token for the caller to continue their query for the next batch of results
+ private RcsQueryContinuationToken mContinuationToken;
+ // The list of participant IDs returned with this query
+ private List<Integer> mParticipants;
+
+ /**
+ * Internal constructor for {@link com.android.internal.telephony.ims.RcsMessageStoreController}
+ * to create query results
+ *
+ * @hide
+ */
+ public RcsParticipantQueryResult(
+ RcsQueryContinuationToken continuationToken,
+ List<Integer> participants) {
+ mContinuationToken = continuationToken;
+ mParticipants = participants;
+ }
+
+ /**
+ * Returns a token to call
+ * {@link RcsMessageStore#getRcsParticipants(RcsQueryContinuationToken)}
+ * to get the next batch of {@link RcsParticipant}s.
+ */
+ @Nullable
+ public RcsQueryContinuationToken getContinuationToken() {
+ return mContinuationToken;
+ }
+
+ /**
+ * Returns all the {@link RcsParticipant}s in the current query result. Call {@link
+ * RcsMessageStore#getRcsParticipants(RcsQueryContinuationToken)} to get the next
+ * batch of {@link RcsParticipant}s.
+ */
+ @NonNull
+ public List<RcsParticipant> getParticipants() {
+ List<RcsParticipant> participantList = new ArrayList<>();
+ for (Integer participantId : mParticipants) {
+ participantList.add(new RcsParticipant(participantId));
+ }
+
+ return participantList;
+ }
+
+ protected RcsParticipantQueryResult(Parcel in) {
+ mContinuationToken = in.readParcelable(
+ RcsQueryContinuationToken.class.getClassLoader());
+ }
+
+ public static final Creator<RcsParticipantQueryResult> CREATOR =
+ new Creator<RcsParticipantQueryResult>() {
+ @Override
+ public RcsParticipantQueryResult createFromParcel(Parcel in) {
+ return new RcsParticipantQueryResult(in);
+ }
+
+ @Override
+ public RcsParticipantQueryResult[] newArray(int size) {
+ return new RcsParticipantQueryResult[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mContinuationToken, flags);
+ }
+}
diff --git a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl b/telephony/java/android/telephony/ims/RcsQueryContinuationToken.aidl
similarity index 94%
copy from telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
copy to telephony/java/android/telephony/ims/RcsQueryContinuationToken.aidl
index 82d985d..319379a 100644
--- a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.aidl
+++ b/telephony/java/android/telephony/ims/RcsQueryContinuationToken.aidl
@@ -17,4 +17,4 @@
package android.telephony.ims;
-parcelable RcsThreadIconChangedEvent;
+parcelable RcsQueryContinuationToken;
diff --git a/telephony/java/android/telephony/ims/RcsQueryContinuationToken.java b/telephony/java/android/telephony/ims/RcsQueryContinuationToken.java
new file mode 100644
index 0000000..e880651
--- /dev/null
+++ b/telephony/java/android/telephony/ims/RcsQueryContinuationToken.java
@@ -0,0 +1,151 @@
+/*
+ * 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.telephony.ims;
+
+import android.annotation.IntDef;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This interface allows using the same implementation for continuation token usage in
+ * {@link com.android.providers.telephony.RcsProvider}
+ * @hide - TODO make getQueryType() and types public - the rest should stay internal
+ */
+public class RcsQueryContinuationToken implements Parcelable {
+ /**
+ * Denotes that this {@link RcsQueryContinuationToken} token is meant to allow continuing
+ * {@link RcsEvent} queries
+ */
+ public static final int EVENT_QUERY_CONTINUATION_TOKEN_TYPE = 0;
+
+ /**
+ * Denotes that this {@link RcsQueryContinuationToken} token is meant to allow continuing
+ * {@link RcsMessage} queries
+ */
+ public static final int MESSAGE_QUERY_CONTINUATION_TOKEN_TYPE = 1;
+
+ /**
+ * Denotes that this {@link RcsQueryContinuationToken} token is meant to allow continuing
+ * {@link RcsParticipant} queries
+ */
+ public static final int PARTICIPANT_QUERY_CONTINUATION_TOKEN_TYPE = 2;
+
+ /**
+ * Denotes that this {@link RcsQueryContinuationToken} token is meant to allow continuing
+ * {@link RcsThread} queries
+ */
+ public static final int THREAD_QUERY_CONTINUATION_TOKEN_TYPE = 3;
+
+ /**
+ * @hide - not meant for public use
+ */
+ public static final String QUERY_CONTINUATION_TOKEN = "query_continuation_token";
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({EVENT_QUERY_CONTINUATION_TOKEN_TYPE, MESSAGE_QUERY_CONTINUATION_TOKEN_TYPE,
+ PARTICIPANT_QUERY_CONTINUATION_TOKEN_TYPE, THREAD_QUERY_CONTINUATION_TOKEN_TYPE})
+ public @interface ContinuationTokenType {}
+
+ // The type of query this token should allow to continue
+ private @ContinuationTokenType int mQueryType;
+ // The raw query string for the initial query
+ private final String mRawQuery;
+ // The number of results that is returned with each query
+ private final int mLimit;
+ // The offset value that this query should start the query from
+ private int mOffset;
+
+ /**
+ * @hide
+ */
+ public RcsQueryContinuationToken(@ContinuationTokenType int queryType, String rawQuery,
+ int limit, int offset) {
+ mQueryType = queryType;
+ mRawQuery = rawQuery;
+ mLimit = limit;
+ mOffset = offset;
+ }
+
+ /**
+ * Returns the original raw query used on {@link com.android.providers.telephony.RcsProvider}
+ * @hide
+ */
+ public String getRawQuery() {
+ return mRawQuery;
+ }
+
+ /**
+ * Returns which index this continuation query should start from
+ * @hide
+ */
+ public int getOffset() {
+ return mOffset;
+ }
+
+ /**
+ * Increments the offset by the amount of result rows returned with the continuation query for
+ * the next query.
+ * @hide
+ */
+ public void incrementOffset() {
+ mOffset += mLimit;
+ }
+
+ /**
+ * Returns the type of query that this {@link RcsQueryContinuationToken} is intended to be used
+ * to continue.
+ */
+ public @ContinuationTokenType int getQueryType() {
+ return mQueryType;
+ }
+
+ protected RcsQueryContinuationToken(Parcel in) {
+ mQueryType = in.readInt();
+ mRawQuery = in.readString();
+ mLimit = in.readInt();
+ mOffset = in.readInt();
+ }
+
+ public static final Creator<RcsQueryContinuationToken> CREATOR =
+ new Creator<RcsQueryContinuationToken>() {
+ @Override
+ public RcsQueryContinuationToken createFromParcel(Parcel in) {
+ return new RcsQueryContinuationToken(in);
+ }
+
+ @Override
+ public RcsQueryContinuationToken[] newArray(int size) {
+ return new RcsQueryContinuationToken[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mQueryType);
+ dest.writeString(mRawQuery);
+ dest.writeInt(mLimit);
+ dest.writeInt(mOffset);
+ }
+}
diff --git a/telephony/java/android/telephony/ims/RcsTextPart.aidl b/telephony/java/android/telephony/ims/RcsTextPart.aidl
deleted file mode 100644
index 4f9fe1f..0000000
--- a/telephony/java/android/telephony/ims/RcsTextPart.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- *
- * Copyright 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.telephony.ims;
-
-parcelable RcsTextPart;
diff --git a/telephony/java/android/telephony/ims/RcsTextPart.java b/telephony/java/android/telephony/ims/RcsTextPart.java
deleted file mode 100644
index 2a72df1..0000000
--- a/telephony/java/android/telephony/ims/RcsTextPart.java
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.telephony.ims;
-
-import android.os.Parcel;
-
-/**
- * A part of a composite {@link RcsMessage} that holds a string
- * @hide - TODO(sahinc) make this public
- */
-public class RcsTextPart extends RcsPart {
- public static final Creator<RcsTextPart> CREATOR = new Creator<RcsTextPart>() {
- @Override
- public RcsTextPart createFromParcel(Parcel in) {
- return new RcsTextPart(in);
- }
-
- @Override
- public RcsTextPart[] newArray(int size) {
- return new RcsTextPart[size];
- }
- };
-
- protected RcsTextPart(Parcel in) {
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- }
-}
diff --git a/telephony/java/android/telephony/ims/RcsThread.aidl b/telephony/java/android/telephony/ims/RcsThread.aidl
deleted file mode 100644
index d9cf6db..0000000
--- a/telephony/java/android/telephony/ims/RcsThread.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- *
- * Copyright 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.telephony;
-
-parcelable RcsThread;
\ No newline at end of file
diff --git a/telephony/java/android/telephony/ims/RcsThread.java b/telephony/java/android/telephony/ims/RcsThread.java
index c0a0d94..238f5e7 100644
--- a/telephony/java/android/telephony/ims/RcsThread.java
+++ b/telephony/java/android/telephony/ims/RcsThread.java
@@ -16,60 +16,117 @@
package android.telephony.ims;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.util.Log;
+import static android.provider.Telephony.RcsColumns.RcsUnifiedThreadColumns.THREAD_TYPE_1_TO_1;
+import static android.provider.Telephony.RcsColumns.RcsUnifiedThreadColumns.THREAD_TYPE_GROUP;
+
+import android.annotation.NonNull;
+import android.annotation.WorkerThread;
+
+import com.android.internal.annotations.VisibleForTesting;
/**
* RcsThread represents a single RCS conversation thread. It holds messages that were sent and
* received and events that occurred on that thread.
- * @hide - TODO(sahinc) make this public
+ *
+ * @hide - TODO(109759350) make this public
*/
-public abstract class RcsThread implements Parcelable {
- // Since this is an abstract class that gets parcelled, the sub-classes need to write these
- // magic values into the parcel so that we know which type to unparcel into.
- protected static final int RCS_1_TO_1_TYPE = 998;
- protected static final int RCS_GROUP_TYPE = 999;
-
+public abstract class RcsThread {
+ // The rcs_participant_thread_id that represents this thread in the database
protected int mThreadId;
+ /**
+ * @hide
+ */
protected RcsThread(int threadId) {
mThreadId = threadId;
}
- protected RcsThread(Parcel in) {
- mThreadId = in.readInt();
+ /**
+ * @return Returns the summary of the latest message in this {@link RcsThread} packaged in an
+ * {@link RcsMessageSnippet} object
+ */
+ @WorkerThread
+ @NonNull
+ public RcsMessageSnippet getSnippet() throws RcsMessageStoreException {
+ return RcsControllerCall.call(iRcs -> iRcs.getMessageSnippet(mThreadId));
}
- public static final Creator<RcsThread> CREATOR = new Creator<RcsThread>() {
- @Override
- public RcsThread createFromParcel(Parcel in) {
- int type = in.readInt();
-
- switch (type) {
- case RCS_1_TO_1_TYPE:
- return new Rcs1To1Thread(in);
- case RCS_GROUP_TYPE:
- return new RcsGroupThread(in);
- default:
- Log.e(RcsMessageStore.TAG, "Cannot unparcel RcsThread, wrong type: " + type);
- }
- return null;
- }
-
- @Override
- public RcsThread[] newArray(int size) {
- return new RcsThread[0];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
+ /**
+ * Adds a new {@link RcsIncomingMessage} to this RcsThread and persists it in storage.
+ *
+ * @throws RcsMessageStoreException if the message could not be persisted into storage.
+ */
+ @WorkerThread
+ @NonNull
+ public RcsIncomingMessage addIncomingMessage(
+ @NonNull RcsIncomingMessageCreationParameters rcsIncomingMessageCreationParameters)
+ throws RcsMessageStoreException {
+ return new RcsIncomingMessage(RcsControllerCall.call(iRcs -> iRcs.addIncomingMessage(
+ mThreadId, rcsIncomingMessageCreationParameters)));
}
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeInt(mThreadId);
+ /**
+ * Adds a new {@link RcsOutgoingMessage} to this RcsThread and persists it in storage.
+ *
+ * @throws RcsMessageStoreException if the message could not be persisted into storage.
+ */
+ @WorkerThread
+ @NonNull
+ public RcsOutgoingMessage addOutgoingMessage(
+ @NonNull RcsMessageCreationParameters rcsMessageCreationParameters)
+ throws RcsMessageStoreException {
+ int messageId = RcsControllerCall.call(iRcs -> iRcs.addOutgoingMessage(
+ mThreadId, rcsMessageCreationParameters));
+
+ return new RcsOutgoingMessage(messageId);
+ }
+
+ /**
+ * Deletes an {@link RcsMessage} from this RcsThread and updates the storage.
+ *
+ * @param rcsMessage The message to delete from the thread
+ * @throws RcsMessageStoreException if the message could not be deleted
+ */
+ @WorkerThread
+ public void deleteMessage(@NonNull RcsMessage rcsMessage) throws RcsMessageStoreException {
+ RcsControllerCall.callWithNoReturn(
+ iRcs -> iRcs.deleteMessage(rcsMessage.getId(), rcsMessage.isIncoming(), mThreadId,
+ isGroup()));
+ }
+
+ /**
+ * Convenience function for loading all the {@link RcsMessage}s in this {@link RcsThread}. For
+ * a more detailed and paginated query, please use
+ * {@link RcsMessageStore#getRcsMessages(RcsMessageQueryParameters)}
+ *
+ * @return Loads the {@link RcsMessage}s in this thread and returns them in an immutable list.
+ * @throws RcsMessageStoreException if the messages could not be read from the storage
+ */
+ @WorkerThread
+ @NonNull
+ public RcsMessageQueryResult getMessages() throws RcsMessageStoreException {
+ RcsMessageQueryParameters queryParameters =
+ new RcsMessageQueryParameters.Builder().setThread(this).build();
+ return RcsControllerCall.call(iRcs -> iRcs.getMessages(queryParameters));
+ }
+
+ /**
+ * @return Returns whether this is a group thread or not
+ */
+ public abstract boolean isGroup();
+
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public int getThreadId() {
+ return mThreadId;
+ }
+
+ /**
+ * @hide
+ */
+ public int getThreadType() {
+ return isGroup() ? THREAD_TYPE_GROUP : THREAD_TYPE_1_TO_1;
}
}
diff --git a/telephony/java/android/telephony/ims/RcsThreadEvent.java b/telephony/java/android/telephony/ims/RcsThreadEvent.java
deleted file mode 100644
index e10baab..0000000
--- a/telephony/java/android/telephony/ims/RcsThreadEvent.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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.telephony.ims;
-
-import android.os.Parcelable;
-
-/**
- * An event that happened on an {@link RcsThread}.
- * @hide - TODO(sahinc) make this public
- */
-public abstract class RcsThreadEvent implements Parcelable {
-}
diff --git a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.java b/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.java
deleted file mode 100644
index b308fef..0000000
--- a/telephony/java/android/telephony/ims/RcsThreadIconChangedEvent.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.telephony.ims;
-
-import android.os.Parcel;
-
-/**
- * An event that indicates an {@link RcsGroupThread}'s icon was changed.
- * @hide - TODO(sahinc) make this public
- */
-public class RcsThreadIconChangedEvent extends RcsThreadEvent {
- public static final Creator<RcsThreadIconChangedEvent> CREATOR =
- new Creator<RcsThreadIconChangedEvent>() {
- @Override
- public RcsThreadIconChangedEvent createFromParcel(Parcel in) {
- return new RcsThreadIconChangedEvent(in);
- }
-
- @Override
- public RcsThreadIconChangedEvent[] newArray(int size) {
- return new RcsThreadIconChangedEvent[size];
- }
- };
-
- protected RcsThreadIconChangedEvent(Parcel in) {
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- }
-}
diff --git a/telephony/java/android/telephony/ims/RcsThreadNameChangedEvent.aidl b/telephony/java/android/telephony/ims/RcsThreadNameChangedEvent.aidl
deleted file mode 100644
index 54a311d..0000000
--- a/telephony/java/android/telephony/ims/RcsThreadNameChangedEvent.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- *
- * Copyright 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.telephony.ims;
-
-parcelable RcsThreadNameChangedEvent;
diff --git a/telephony/java/android/telephony/ims/RcsThreadNameChangedEvent.java b/telephony/java/android/telephony/ims/RcsThreadNameChangedEvent.java
deleted file mode 100644
index 6f5cfdf..0000000
--- a/telephony/java/android/telephony/ims/RcsThreadNameChangedEvent.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.telephony.ims;
-
-import android.os.Parcel;
-
-/**
- * An event that indicates an {@link RcsGroupThread}'s name was changed.
- * @hide - TODO(sahinc) make this public
- */
-public class RcsThreadNameChangedEvent extends RcsThreadEvent {
- public static final Creator<RcsThreadNameChangedEvent> CREATOR =
- new Creator<RcsThreadNameChangedEvent>() {
- @Override
- public RcsThreadNameChangedEvent createFromParcel(Parcel in) {
- return new RcsThreadNameChangedEvent(in);
- }
-
- @Override
- public RcsThreadNameChangedEvent[] newArray(int size) {
- return new RcsThreadNameChangedEvent[size];
- }
- };
-
- protected RcsThreadNameChangedEvent(Parcel in) {
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- }
-}
diff --git a/telephony/java/android/telephony/ims/RcsThreadParticipantJoinedEvent.aidl b/telephony/java/android/telephony/ims/RcsThreadParticipantJoinedEvent.aidl
deleted file mode 100644
index 047a424..0000000
--- a/telephony/java/android/telephony/ims/RcsThreadParticipantJoinedEvent.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- *
- * Copyright 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.telephony.ims;
-
-parcelable RcsThreadParticipantJoinedEvent;
diff --git a/telephony/java/android/telephony/ims/RcsThreadParticipantJoinedEvent.java b/telephony/java/android/telephony/ims/RcsThreadParticipantJoinedEvent.java
deleted file mode 100644
index 5c4073c..0000000
--- a/telephony/java/android/telephony/ims/RcsThreadParticipantJoinedEvent.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.telephony.ims;
-
-import android.os.Parcel;
-
-/**
- * An event that indicates an RCS participant has joined an {@link RcsGroupThread}.
- * @hide - TODO(sahinc) make this public
- */
-public class RcsThreadParticipantJoinedEvent extends RcsThreadEvent {
- public static final Creator<RcsThreadParticipantJoinedEvent> CREATOR =
- new Creator<RcsThreadParticipantJoinedEvent>() {
- @Override
- public RcsThreadParticipantJoinedEvent createFromParcel(Parcel in) {
- return new RcsThreadParticipantJoinedEvent(in);
- }
-
- @Override
- public RcsThreadParticipantJoinedEvent[] newArray(int size) {
- return new RcsThreadParticipantJoinedEvent[size];
- }
- };
-
- protected RcsThreadParticipantJoinedEvent(Parcel in) {
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- }
-}
diff --git a/telephony/java/android/telephony/ims/RcsThreadParticipantLeftEvent.aidl b/telephony/java/android/telephony/ims/RcsThreadParticipantLeftEvent.aidl
deleted file mode 100644
index 52f9bbd..0000000
--- a/telephony/java/android/telephony/ims/RcsThreadParticipantLeftEvent.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- *
- * Copyright 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.telephony.ims;
-
-parcelable RcsThreadParticipantLeftEvent;
diff --git a/telephony/java/android/telephony/ims/RcsThreadParticipantLeftEvent.java b/telephony/java/android/telephony/ims/RcsThreadParticipantLeftEvent.java
deleted file mode 100644
index 4bf86b9..0000000
--- a/telephony/java/android/telephony/ims/RcsThreadParticipantLeftEvent.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * 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.telephony.ims;
-
-import android.os.Parcel;
-
-/**
- * An event that indicates an RCS participant has left an {@link RcsGroupThread}.
- * @hide - TODO(sahinc) make this public
- */
-public class RcsThreadParticipantLeftEvent extends RcsThreadEvent {
- public static final Creator<RcsThreadParticipantLeftEvent> CREATOR =
- new Creator<RcsThreadParticipantLeftEvent>() {
- @Override
- public RcsThreadParticipantLeftEvent createFromParcel(Parcel in) {
- return new RcsThreadParticipantLeftEvent(in);
- }
-
- @Override
- public RcsThreadParticipantLeftEvent[] newArray(int size) {
- return new RcsThreadParticipantLeftEvent[size];
- }
- };
-
- protected RcsThreadParticipantLeftEvent(Parcel in) {
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- }
-}
diff --git a/telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.aidl b/telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.aidl
deleted file mode 100644
index 7bcebfa..0000000
--- a/telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
-**
-** Copyright 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.telephony.ims;
-
-parcelable RcsThreadQueryContinuationToken;
diff --git a/telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.java b/telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.java
deleted file mode 100644
index 931e93d..0000000
--- a/telephony/java/android/telephony/ims/RcsThreadQueryContinuationToken.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.telephony.ims;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * A continuation token to provide for {@link RcsMessageStore#getRcsThreads}. Use this token to
- * break large queries into manageable chunks
- * @hide - TODO make this public
- */
-public class RcsThreadQueryContinuationToken implements Parcelable {
- protected RcsThreadQueryContinuationToken(Parcel in) {
- }
-
- public static final Creator<RcsThreadQueryContinuationToken> CREATOR =
- new Creator<RcsThreadQueryContinuationToken>() {
- @Override
- public RcsThreadQueryContinuationToken createFromParcel(Parcel in) {
- return new RcsThreadQueryContinuationToken(in);
- }
-
- @Override
- public RcsThreadQueryContinuationToken[] newArray(int size) {
- return new RcsThreadQueryContinuationToken[size];
- }
- };
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- }
-}
diff --git a/telephony/java/android/telephony/ims/RcsThreadQueryParameters.aidl b/telephony/java/android/telephony/ims/RcsThreadQueryParameters.aidl
index feb2d4d..52e73ce 100644
--- a/telephony/java/android/telephony/ims/RcsThreadQueryParameters.aidl
+++ b/telephony/java/android/telephony/ims/RcsThreadQueryParameters.aidl
@@ -1,19 +1,19 @@
/*
-**
-** Copyright 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.
-*/
+ *
+ * Copyright 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.telephony.ims;
diff --git a/telephony/java/android/telephony/ims/RcsThreadQueryParameters.java b/telephony/java/android/telephony/ims/RcsThreadQueryParameters.java
index f2c4ab1..4aa4207 100644
--- a/telephony/java/android/telephony/ims/RcsThreadQueryParameters.java
+++ b/telephony/java/android/telephony/ims/RcsThreadQueryParameters.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -17,72 +17,133 @@
package android.telephony.ims;
import android.annotation.CheckResult;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.security.InvalidParameterException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
/**
* The parameters to pass into {@link RcsMessageStore#getRcsThreads(RcsThreadQueryParameters)} in
* order to select a subset of {@link RcsThread}s present in the message store.
+ *
* @hide TODO - make the Builder and builder() public. The rest should stay internal only.
*/
public class RcsThreadQueryParameters implements Parcelable {
- private final boolean mIsGroup;
- private final Set<RcsParticipant> mRcsParticipants;
+ /**
+ * Bitmask flag to be used with {@link Builder#setThreadType(int)} to make
+ * {@link RcsMessageStore#getRcsThreads(RcsThreadQueryParameters)} return
+ * {@link RcsGroupThread}s.
+ */
+ public static final int THREAD_TYPE_GROUP = 0x0001;
+
+ /**
+ * Bitmask flag to be used with {@link Builder#setThreadType(int)} to make
+ * {@link RcsMessageStore#getRcsThreads(RcsThreadQueryParameters)} return
+ * {@link Rcs1To1Thread}s.
+ */
+ public static final int THREAD_TYPE_1_TO_1 = 0x0002;
+
+ // The type of threads to be filtered with the query
+ private final int mThreadType;
+ // The list of participants that are expected in the resulting threads
+ private final List<Integer> mRcsParticipantIds;
+ // The number of RcsThread's that should be returned with this query
private final int mLimit;
+ // The property which the result of the query should be sorted against
+ private final @SortingProperty int mSortingProperty;
+ // Whether the sorting should be done in ascending
private final boolean mIsAscending;
- RcsThreadQueryParameters(boolean isGroup, Set<RcsParticipant> participants, int limit,
- boolean isAscending) {
- mIsGroup = isGroup;
- mRcsParticipants = participants;
+ /**
+ * Flag to be used with {@link Builder#setSortProperty(int)} to denote that the results should
+ * be sorted in the order of {@link RcsThread} creation time for faster results.
+ */
+ public static final int SORT_BY_CREATION_ORDER = 0;
+
+ /**
+ * Flag to be used with {@link Builder#setSortProperty(int)} to denote that the results should
+ * be sorted according to the timestamp of {@link RcsThread#getSnippet()}
+ */
+ public static final int SORT_BY_TIMESTAMP = 1;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SORT_BY_CREATION_ORDER, SORT_BY_TIMESTAMP})
+ public @interface SortingProperty {
+ }
+
+ /**
+ * @hide
+ */
+ public static final String THREAD_QUERY_PARAMETERS_KEY = "thread_query_parameters";
+
+ RcsThreadQueryParameters(int threadType, Set<RcsParticipant> participants,
+ int limit, int sortingProperty, boolean isAscending) {
+ mThreadType = threadType;
+ mRcsParticipantIds = convertParticipantSetToIdList(participants);
mLimit = limit;
+ mSortingProperty = sortingProperty;
mIsAscending = isAscending;
}
- /**
- * Returns a new builder to build a query with.
- * TODO - make public
- */
- public static Builder builder() {
- return new Builder();
+ private static List<Integer> convertParticipantSetToIdList(Set<RcsParticipant> participants) {
+ List<Integer> ids = new ArrayList<>(participants.size());
+ for (RcsParticipant participant : participants) {
+ ids.add(participant.getId());
+ }
+ return ids;
}
/**
* This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to get
- * the list of participants.
- * @hide
+ * the list of participant IDs.
+ *
+ * As we don't expose any integer ID's to API users, this should stay hidden
+ *
+ * @hide - not meant for public use
*/
- public Set<RcsParticipant> getRcsParticipants() {
- return mRcsParticipants;
+ public List<Integer> getRcsParticipantsIds() {
+ return Collections.unmodifiableList(mRcsParticipantIds);
}
/**
- * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to get
- * whether group threads should be queried
- * @hide
+ * @return Returns the bitmask flag for types of {@link RcsThread}s that this query should
+ * return.
*/
- public boolean isGroupThread() {
- return mIsGroup;
+ public int getThreadType() {
+ return mThreadType;
}
/**
- * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to get
- * the number of tuples the result query should be limited to.
+ * @return Returns the number of {@link RcsThread}s to be returned from the query. A value
+ * of 0 means there is no set limit.
*/
public int getLimit() {
return mLimit;
}
/**
- * This is used in {@link com.android.internal.telephony.ims.RcsMessageStoreController} to
- * determine the sort order.
+ * @return Returns the property that will be used to sort the result against.
+ * @see SortingProperty
*/
- public boolean isAscending() {
+ public @SortingProperty int getSortingProperty() {
+ return mSortingProperty;
+ }
+
+ /**
+ * @return Returns {@code true} if the result set will be sorted in ascending order,
+ * {@code false} if it will be sorted in descending order.
+ */
+ public boolean getSortDirection() {
return mIsAscending;
}
@@ -90,64 +151,74 @@
* A helper class to build the {@link RcsThreadQueryParameters}.
*/
public static class Builder {
- private boolean mIsGroupThread;
+ private int mThreadType;
private Set<RcsParticipant> mParticipants;
private int mLimit = 100;
+ private @SortingProperty int mSortingProperty;
private boolean mIsAscending;
/**
- * Package private constructor for {@link RcsThreadQueryParameters.Builder}. To obtain this,
- * {@link RcsThreadQueryParameters#builder()} needs to be called.
+ * Constructs a {@link RcsThreadQueryParameters.Builder} to help build an
+ * {@link RcsThreadQueryParameters}
*/
- Builder() {
+ public Builder() {
mParticipants = new HashSet<>();
}
/**
* Limits the query to only return group threads.
- * @param isGroupThread Whether to limit the query result to group threads.
+ *
+ * @param threadType Whether to limit the query result to group threads.
* @return The same instance of the builder to chain parameters.
+ * @see RcsThreadQueryParameters#THREAD_TYPE_GROUP
+ * @see RcsThreadQueryParameters#THREAD_TYPE_1_TO_1
*/
@CheckResult
- public Builder isGroupThread(boolean isGroupThread) {
- mIsGroupThread = isGroupThread;
+ public Builder setThreadType(int threadType) {
+ mThreadType = threadType;
return this;
}
/**
- * Limits the query to only return threads that contain the given participant.
+ * Limits the query to only return threads that contain the given participant. If this
+ * property was not set, participants will not be taken into account while querying for
+ * threads.
+ *
* @param participant The participant that must be included in all of the returned threads.
* @return The same instance of the builder to chain parameters.
*/
@CheckResult
- public Builder withParticipant(RcsParticipant participant) {
+ public Builder setParticipant(@NonNull RcsParticipant participant) {
mParticipants.add(participant);
return this;
}
/**
- * Limits the query to only return threads that contain the given list of participants.
+ * Limits the query to only return threads that contain the given list of participants. If
+ * this property was not set, participants will not be taken into account while querying
+ * for threads.
+ *
* @param participants An iterable list of participants that must be included in all of the
* returned threads.
* @return The same instance of the builder to chain parameters.
*/
@CheckResult
- public Builder withParticipants(Iterable<RcsParticipant> participants) {
- for (RcsParticipant participant : participants) {
- mParticipants.add(participant);
- }
+ public Builder setParticipants(@NonNull List<RcsParticipant> participants) {
+ mParticipants.addAll(participants);
return this;
}
/**
* Desired number of threads to be returned from the query. Passing in 0 will return all
* existing threads at once. The limit defaults to 100.
+ *
* @param limit The number to limit the query result to.
* @return The same instance of the builder to chain parameters.
* @throws InvalidParameterException If the given limit is negative.
*/
@CheckResult
- public Builder limitResultsTo(int limit) throws InvalidParameterException {
+ public Builder setResultLimit(@IntRange(from = 0) int limit)
+ throws InvalidParameterException {
if (limit < 0) {
throw new InvalidParameterException("The query limit must be non-negative");
}
@@ -157,15 +228,26 @@
}
/**
- * Sorts the results returned from the query via thread IDs.
+ * Sets the property where the results should be sorted against. Defaults to
+ * {@link SortingProperty#SORT_BY_CREATION_ORDER}
*
- * TODO - add sorting support for other fields
- *
- * @param isAscending whether to sort in ascending order or not
+ * @param sortingProperty whether to sort in ascending order or not
* @return The same instance of the builder to chain parameters.
*/
@CheckResult
- public Builder sort(boolean isAscending) {
+ public Builder setSortProperty(@SortingProperty int sortingProperty) {
+ mSortingProperty = sortingProperty;
+ return this;
+ }
+
+ /**
+ * Sets whether the results should be sorted ascending or descending
+ *
+ * @param isAscending whether the results should be sorted ascending
+ * @return The same instance of the builder to chain parameters.
+ */
+ @CheckResult
+ public Builder setSortDirection(boolean isAscending) {
mIsAscending = isAscending;
return this;
}
@@ -177,8 +259,8 @@
* @return An instance of {@link RcsThreadQueryParameters} to use with the thread query.
*/
public RcsThreadQueryParameters build() {
- return new RcsThreadQueryParameters(
- mIsGroupThread, mParticipants, mLimit, mIsAscending);
+ return new RcsThreadQueryParameters(mThreadType, mParticipants, mLimit,
+ mSortingProperty, mIsAscending);
}
}
@@ -186,14 +268,12 @@
* Parcelable boilerplate below.
*/
protected RcsThreadQueryParameters(Parcel in) {
- mIsGroup = in.readBoolean();
-
- ArrayList<RcsParticipant> participantArrayList = new ArrayList<>();
- in.readTypedList(participantArrayList, RcsParticipant.CREATOR);
- mRcsParticipants = new HashSet<>(participantArrayList);
-
+ mThreadType = in.readInt();
+ mRcsParticipantIds = new ArrayList<>();
+ in.readList(mRcsParticipantIds, Integer.class.getClassLoader());
mLimit = in.readInt();
- mIsAscending = in.readBoolean();
+ mSortingProperty = in.readInt();
+ mIsAscending = in.readByte() == 1;
}
public static final Creator<RcsThreadQueryParameters> CREATOR =
@@ -216,10 +296,10 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeBoolean(mIsGroup);
- dest.writeTypedList(new ArrayList<>(mRcsParticipants));
+ dest.writeInt(mThreadType);
+ dest.writeList(mRcsParticipantIds);
dest.writeInt(mLimit);
- dest.writeBoolean(mIsAscending);
+ dest.writeInt(mSortingProperty);
+ dest.writeByte((byte) (mIsAscending ? 1 : 0));
}
-
}
diff --git a/telephony/java/android/telephony/ims/RcsThreadQueryResult.aidl b/telephony/java/android/telephony/ims/RcsThreadQueryResult.aidl
index 4b06529..b1d5cf4 100644
--- a/telephony/java/android/telephony/ims/RcsThreadQueryResult.aidl
+++ b/telephony/java/android/telephony/ims/RcsThreadQueryResult.aidl
@@ -1,19 +1,19 @@
/*
-**
-** Copyright 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.
-*/
+ *
+ * Copyright 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.telephony.ims;
diff --git a/telephony/java/android/telephony/ims/RcsThreadQueryResult.java b/telephony/java/android/telephony/ims/RcsThreadQueryResult.java
index 47715f8..6515933 100644
--- a/telephony/java/android/telephony/ims/RcsThreadQueryResult.java
+++ b/telephony/java/android/telephony/ims/RcsThreadQueryResult.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -16,22 +16,30 @@
package android.telephony.ims;
+import static android.provider.Telephony.RcsColumns.RcsUnifiedThreadColumns.THREAD_TYPE_1_TO_1;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.ims.RcsTypeIdPair;
+
+import java.util.ArrayList;
import java.util.List;
/**
- * The result of a {@link RcsMessageStore#getRcsThreads(RcsThreadQueryContinuationToken,
- * RcsThreadQueryParameters)}
+ * The result of a {@link RcsMessageStore#getRcsThreads(RcsThreadQueryParameters)}
* call. This class allows getting the token for querying the next batch of threads in order to
* prevent handling large amounts of data at once.
*
* @hide
*/
public class RcsThreadQueryResult implements Parcelable {
- private RcsThreadQueryContinuationToken mContinuationToken;
- private List<RcsThread> mRcsThreads;
+ // A token for the caller to continue their query for the next batch of results
+ private RcsQueryContinuationToken mContinuationToken;
+ // The list of thread IDs returned with this query
+ private List<RcsTypeIdPair> mRcsThreadIds;
/**
* Internal constructor for {@link com.android.internal.telephony.ims.RcsMessageStoreController}
@@ -40,31 +48,47 @@
* @hide
*/
public RcsThreadQueryResult(
- RcsThreadQueryContinuationToken continuationToken, List<RcsThread> rcsThreads) {
+ RcsQueryContinuationToken continuationToken,
+ List<RcsTypeIdPair> rcsThreadIds) {
mContinuationToken = continuationToken;
- mRcsThreads = rcsThreads;
+ mRcsThreadIds = rcsThreadIds;
}
/**
* Returns a token to call
- * {@link RcsMessageStore#getRcsThreads(RcsThreadQueryContinuationToken)}
+ * {@link RcsMessageStore#getRcsThreads(RcsQueryContinuationToken)}
* to get the next batch of {@link RcsThread}s.
*/
- public RcsThreadQueryContinuationToken nextChunkToken() {
+ @Nullable
+ public RcsQueryContinuationToken getContinuationToken() {
return mContinuationToken;
}
/**
* Returns all the RcsThreads in the current query result. Call {@link
- * RcsMessageStore#getRcsThreads(RcsThreadQueryContinuationToken)} to get the next batch of
+ * RcsMessageStore#getRcsThreads(RcsQueryContinuationToken)} to get the next batch of
* {@link RcsThread}s.
*/
+ @NonNull
public List<RcsThread> getThreads() {
- return mRcsThreads;
+ List<RcsThread> rcsThreads = new ArrayList<>();
+
+ for (RcsTypeIdPair typeIdPair : mRcsThreadIds) {
+ if (typeIdPair.getType() == THREAD_TYPE_1_TO_1) {
+ rcsThreads.add(new Rcs1To1Thread(typeIdPair.getId()));
+ } else {
+ rcsThreads.add(new RcsGroupThread(typeIdPair.getId()));
+ }
+ }
+
+ return rcsThreads;
}
protected RcsThreadQueryResult(Parcel in) {
- // TODO - implement
+ mContinuationToken = in.readParcelable(
+ RcsQueryContinuationToken.class.getClassLoader());
+ mRcsThreadIds = new ArrayList<>();
+ in.readList(mRcsThreadIds, Integer.class.getClassLoader());
}
public static final Creator<RcsThreadQueryResult> CREATOR =
@@ -87,6 +111,7 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
- // TODO - implement
+ dest.writeParcelable(mContinuationToken, flags);
+ dest.writeList(mRcsThreadIds);
}
}
diff --git a/telephony/java/android/telephony/ims/aidl/IRcs.aidl b/telephony/java/android/telephony/ims/aidl/IRcs.aidl
index 0c958ba..a399786 100644
--- a/telephony/java/android/telephony/ims/aidl/IRcs.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IRcs.aidl
@@ -16,9 +16,18 @@
package android.telephony.ims.aidl;
-import android.telephony.ims.RcsParticipant;
-import android.telephony.ims.Rcs1To1Thread;
-import android.telephony.ims.RcsThreadQueryContinuationToken;
+import android.net.Uri;
+import android.telephony.ims.RcsEventQueryParameters;
+import android.telephony.ims.RcsEventQueryResult;
+import android.telephony.ims.RcsFileTransferCreationParameters;
+import android.telephony.ims.RcsIncomingMessageCreationParameters;
+import android.telephony.ims.RcsMessageCreationParameters;
+import android.telephony.ims.RcsMessageSnippet;
+import android.telephony.ims.RcsMessageQueryParameters;
+import android.telephony.ims.RcsMessageQueryResult;
+import android.telephony.ims.RcsParticipantQueryParameters;
+import android.telephony.ims.RcsParticipantQueryResult;
+import android.telephony.ims.RcsQueryContinuationToken;
import android.telephony.ims.RcsThreadQueryParameters;
import android.telephony.ims.RcsThreadQueryResult;
@@ -27,23 +36,231 @@
* {@hide}
*/
interface IRcs {
+ /////////////////////////
// RcsMessageStore APIs
+ /////////////////////////
RcsThreadQueryResult getRcsThreads(in RcsThreadQueryParameters queryParameters);
RcsThreadQueryResult getRcsThreadsWithToken(
- in RcsThreadQueryContinuationToken continuationToken);
+ in RcsQueryContinuationToken continuationToken);
- void deleteThread(int threadId);
+ RcsParticipantQueryResult getParticipants(in RcsParticipantQueryParameters queryParameters);
- Rcs1To1Thread createRcs1To1Thread(in RcsParticipant participant);
+ RcsParticipantQueryResult getParticipantsWithToken(
+ in RcsQueryContinuationToken continuationToken);
+ RcsMessageQueryResult getMessages(in RcsMessageQueryParameters queryParameters);
+
+ RcsMessageQueryResult getMessagesWithToken(
+ in RcsQueryContinuationToken continuationToken);
+
+ RcsEventQueryResult getEvents(in RcsEventQueryParameters queryParameters);
+
+ RcsEventQueryResult getEventsWithToken(
+ in RcsQueryContinuationToken continuationToken);
+
+ // returns true if the thread was successfully deleted
+ boolean deleteThread(int threadId, int threadType);
+
+ // Creates an Rcs1To1Thread and returns its row ID
+ int createRcs1To1Thread(int participantId);
+
+ // Creates an RcsGroupThread and returns its row ID
+ int createGroupThread(in int[] participantIds, String groupName, in Uri groupIcon);
+
+ /////////////////////////
// RcsThread APIs
- int getMessageCount(int rcsThreadId);
+ /////////////////////////
+ // Creates a new RcsIncomingMessage on the given thread and returns its row ID
+ int addIncomingMessage(int rcsThreadId,
+ in RcsIncomingMessageCreationParameters rcsIncomingMessageCreationParameters);
+
+ // Creates a new RcsOutgoingMessage on the given thread and returns its row ID
+ int addOutgoingMessage(int rcsThreadId,
+ in RcsMessageCreationParameters rcsMessageCreationParameters);
+
+ // TODO: modify RcsProvider URI's to allow deleting a message without specifying its thread
+ void deleteMessage(int rcsMessageId, boolean isIncoming, int rcsThreadId, boolean isGroup);
+
+ RcsMessageSnippet getMessageSnippet(int rcsThreadId);
+
+ /////////////////////////
+ // Rcs1To1Thread APIs
+ /////////////////////////
+ void set1To1ThreadFallbackThreadId(int rcsThreadId, long fallbackId);
+
+ long get1To1ThreadFallbackThreadId(int rcsThreadId);
+
+ int get1To1ThreadOtherParticipantId(int rcsThreadId);
+
+ /////////////////////////
+ // RcsGroupThread APIs
+ /////////////////////////
+ void setGroupThreadName(int rcsThreadId, String groupName);
+
+ String getGroupThreadName(int rcsThreadId);
+
+ void setGroupThreadIcon(int rcsThreadId, in Uri groupIcon);
+
+ Uri getGroupThreadIcon(int rcsThreadId);
+
+ void setGroupThreadOwner(int rcsThreadId, int participantId);
+
+ int getGroupThreadOwner(int rcsThreadId);
+
+ void setGroupThreadConferenceUri(int rcsThreadId, in Uri conferenceUri);
+
+ Uri getGroupThreadConferenceUri(int rcsThreadId);
+
+ void addParticipantToGroupThread(int rcsThreadId, int participantId);
+
+ void removeParticipantFromGroupThread(int rcsThreadId, int participantId);
+
+ /////////////////////////
// RcsParticipant APIs
- RcsParticipant createRcsParticipant(String canonicalAddress);
+ /////////////////////////
- void updateRcsParticipantCanonicalAddress(int id, String canonicalAddress);
+ // Creates a new RcsParticipant and returns its rowId
+ int createRcsParticipant(String canonicalAddress, String alias);
- void updateRcsParticipantAlias(int id, String alias);
+ String getRcsParticipantCanonicalAddress(int participantId);
+
+ String getRcsParticipantAlias(int participantId);
+
+ void setRcsParticipantAlias(int id, String alias);
+
+ String getRcsParticipantContactId(int participantId);
+
+ void setRcsParticipantContactId(int participantId, String contactId);
+
+ /////////////////////////
+ // RcsMessage APIs
+ /////////////////////////
+ void setMessageSubId(int messageId, boolean isIncoming, int subId);
+
+ int getMessageSubId(int messageId, boolean isIncoming);
+
+ void setMessageStatus(int messageId, boolean isIncoming, int status);
+
+ int getMessageStatus(int messageId, boolean isIncoming);
+
+ void setMessageOriginationTimestamp(int messageId, boolean isIncoming, long originationTimestamp);
+
+ long getMessageOriginationTimestamp(int messageId, boolean isIncoming);
+
+ void setGlobalMessageIdForMessage(int messageId, boolean isIncoming, String globalId);
+
+ String getGlobalMessageIdForMessage(int messageId, boolean isIncoming);
+
+ void setMessageArrivalTimestamp(int messageId, boolean isIncoming, long arrivalTimestamp);
+
+ long getMessageArrivalTimestamp(int messageId, boolean isIncoming);
+
+ void setMessageSeenTimestamp(int messageId, boolean isIncoming, long seenTimestamp);
+
+ long getMessageSeenTimestamp(int messageId, boolean isIncoming);
+
+ void setTextForMessage(int messageId, boolean isIncoming, String text);
+
+ String getTextForMessage(int messageId, boolean isIncoming);
+
+ void setLatitudeForMessage(int messageId, boolean isIncoming, double latitude);
+
+ double getLatitudeForMessage(int messageId, boolean isIncoming);
+
+ void setLongitudeForMessage(int messageId, boolean isIncoming, double longitude);
+
+ double getLongitudeForMessage(int messageId, boolean isIncoming);
+
+ // Returns the ID's of the file transfers attached to the given message
+ int[] getFileTransfersAttachedToMessage(int messageId, boolean isIncoming);
+
+ int getSenderParticipant(int messageId);
+
+ /////////////////////////
+ // RcsOutgoingMessageDelivery APIs
+ /////////////////////////
+
+ // Returns the participant ID's that this message is intended to be delivered to
+ int[] getMessageRecipients(int messageId);
+
+ long getOutgoingDeliveryDeliveredTimestamp(int messageId, int participantId);
+
+ void setOutgoingDeliveryDeliveredTimestamp(int messageId, int participantId, long deliveredTimestamp);
+
+ long getOutgoingDeliverySeenTimestamp(int messageId, int participantId);
+
+ void setOutgoingDeliverySeenTimestamp(int messageId, int participantId, long seenTimestamp);
+
+ int getOutgoingDeliveryStatus(int messageId, int participantId);
+
+ void setOutgoingDeliveryStatus(int messageId, int participantId, int status);
+
+ /////////////////////////
+ // RcsFileTransferPart APIs
+ /////////////////////////
+
+ // Performs the initial write to storage and returns the row ID.
+ int storeFileTransfer(int messageId, boolean isIncoming,
+ in RcsFileTransferCreationParameters fileTransferCreationParameters);
+
+ void deleteFileTransfer(int partId);
+
+ void setFileTransferSessionId(int partId, String sessionId);
+
+ String getFileTransferSessionId(int partId);
+
+ void setFileTransferContentUri(int partId, in Uri contentUri);
+
+ Uri getFileTransferContentUri(int partId);
+
+ void setFileTransferContentType(int partId, String contentType);
+
+ String getFileTransferContentType(int partId);
+
+ void setFileTransferFileSize(int partId, long fileSize);
+
+ long getFileTransferFileSize(int partId);
+
+ void setFileTransferTransferOffset(int partId, long transferOffset);
+
+ long getFileTransferTransferOffset(int partId);
+
+ void setFileTransferStatus(int partId, int transferStatus);
+
+ int getFileTransferStatus(int partId);
+
+ void setFileTransferWidth(int partId, int width);
+
+ int getFileTransferWidth(int partId);
+
+ void setFileTransferHeight(int partId, int height);
+
+ int getFileTransferHeight(int partId);
+
+ void setFileTransferLength(int partId, long length);
+
+ long getFileTransferLength(int partId);
+
+ void setFileTransferPreviewUri(int partId, in Uri uri);
+
+ Uri getFileTransferPreviewUri(int partId);
+
+ void setFileTransferPreviewType(int partId, String type);
+
+ String getFileTransferPreviewType(int partId);
+
+ /////////////////////////
+ // RcsEvent APIs
+ /////////////////////////
+ int createGroupThreadNameChangedEvent(long timestamp, int threadId, int originationParticipantId, String newName);
+
+ int createGroupThreadIconChangedEvent(long timestamp, int threadId, int originationParticipantId, in Uri newIcon);
+
+ int createGroupThreadParticipantJoinedEvent(long timestamp, int threadId, int originationParticipantId, int participantId);
+
+ int createGroupThreadParticipantLeftEvent(long timestamp, int threadId, int originationParticipantId, int participantId);
+
+ int createParticipantAliasChangedEvent(long timestamp, int participantId, String newAlias);
}
\ No newline at end of file
diff --git a/telephony/java/com/android/ims/RcsTypeIdPair.java b/telephony/java/com/android/ims/RcsTypeIdPair.java
new file mode 100644
index 0000000..a517735
--- /dev/null
+++ b/telephony/java/com/android/ims/RcsTypeIdPair.java
@@ -0,0 +1,79 @@
+/*
+ * 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.ims;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A utility class to pass RCS IDs and types in RPC calls
+ *
+ * @hide
+ */
+public class RcsTypeIdPair implements Parcelable {
+ private int mType;
+ private int mId;
+
+ public RcsTypeIdPair(int type, int id) {
+ mType = type;
+ mId = id;
+ }
+
+ public int getType() {
+ return mType;
+ }
+
+ public void setType(int type) {
+ mType = type;
+ }
+
+ public int getId() {
+ return mId;
+ }
+
+ public void setId(int id) {
+ mId = id;
+ }
+
+ public RcsTypeIdPair(Parcel in) {
+ mType = in.readInt();
+ mId = in.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(mType);
+ dest.writeInt(mId);
+ }
+
+ public static final Creator<RcsTypeIdPair> CREATOR =
+ new Creator<RcsTypeIdPair>() {
+ @Override
+ public RcsTypeIdPair createFromParcel(Parcel in) {
+ return new RcsTypeIdPair(in);
+ }
+
+ @Override
+ public RcsTypeIdPair[] newArray(int size) {
+ return new RcsTypeIdPair[size];
+ }
+ };
+}
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
index 964a313..9080e23 100644
--- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -21,7 +21,6 @@
import android.telephony.PhoneNumberUtils;
import android.telephony.SmsCbLocation;
import android.telephony.SmsCbMessage;
-import android.telephony.TelephonyManager;
import android.telephony.cdma.CdmaSmsCbProgramData;
import android.telephony.Rlog;
import android.util.Log;
@@ -746,8 +745,10 @@
/**
* Parses a broadcast SMS, possibly containing a CMAS alert.
+ *
+ * @param plmn the PLMN for a broadcast SMS
*/
- public SmsCbMessage parseBroadcastSms() {
+ public SmsCbMessage parseBroadcastSms(String plmn) {
BearerData bData = BearerData.decode(mEnvelope.bearerData, mEnvelope.serviceCategory);
if (bData == null) {
Rlog.w(LOG_TAG, "BearerData.decode() returned null");
@@ -758,7 +759,6 @@
Rlog.d(LOG_TAG, "MT raw BearerData = " + HexDump.toHexString(mEnvelope.bearerData));
}
- String plmn = TelephonyManager.getDefault().getNetworkOperator();
SmsCbLocation location = new SmsCbLocation(plmn);
return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP2,
@@ -858,11 +858,11 @@
bearerData.userData = userData;
byte[] encodedBearerData = BearerData.encode(bearerData);
+ if (encodedBearerData == null) return null;
if (Rlog.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
Rlog.d(LOG_TAG, "MO (encoded) BearerData = " + bearerData);
Rlog.d(LOG_TAG, "MO raw BearerData = '" + HexDump.toHexString(encodedBearerData) + "'");
}
- if (encodedBearerData == null) return null;
int teleservice = bearerData.hasUserDataHeader ?
SmsEnvelope.TELESERVICE_WEMT : SmsEnvelope.TELESERVICE_WMT;
diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadIconChangedEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadIconChangedEventTest.java
new file mode 100644
index 0000000..915a260
--- /dev/null
+++ b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadIconChangedEventTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.tests.ims;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.ims.RcsGroupThread;
+import android.telephony.ims.RcsGroupThreadIconChangedEvent;
+import android.telephony.ims.RcsParticipant;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class RcsGroupThreadIconChangedEventTest {
+
+ @Test
+ public void testCanUnparcel() {
+ RcsGroupThread rcsGroupThread = new RcsGroupThread(1);
+ RcsParticipant rcsParticipant = new RcsParticipant(2);
+ Uri newIconUri = Uri.parse("content://new_icon");
+
+ RcsGroupThreadIconChangedEvent iconChangedEvent =
+ new RcsGroupThreadIconChangedEvent(1234567890, rcsGroupThread, rcsParticipant,
+ newIconUri);
+
+ Parcel parcel = Parcel.obtain();
+ iconChangedEvent.writeToParcel(parcel, iconChangedEvent.describeContents());
+
+ parcel.setDataPosition(0);
+
+ iconChangedEvent = RcsGroupThreadIconChangedEvent.CREATOR.createFromParcel(parcel);
+
+ assertThat(iconChangedEvent.getNewIcon()).isEqualTo(newIconUri);
+ assertThat(iconChangedEvent.getRcsGroupThread().getThreadId()).isEqualTo(1);
+ assertThat(iconChangedEvent.getOriginatingParticipant().getId()).isEqualTo(2);
+ assertThat(iconChangedEvent.getTimestamp()).isEqualTo(1234567890);
+ }
+}
diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadNameChangedEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadNameChangedEventTest.java
new file mode 100644
index 0000000..1384c01
--- /dev/null
+++ b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadNameChangedEventTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.tests.ims;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.ims.RcsGroupThread;
+import android.telephony.ims.RcsGroupThreadNameChangedEvent;
+import android.telephony.ims.RcsParticipant;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class RcsGroupThreadNameChangedEventTest {
+ @Test
+ public void testCanUnparcel() {
+ String newName = "new name";
+
+ RcsGroupThread rcsGroupThread = new RcsGroupThread(1);
+ RcsParticipant rcsParticipant = new RcsParticipant(2);
+
+ RcsGroupThreadNameChangedEvent nameChangedEvent =
+ new RcsGroupThreadNameChangedEvent(1234567890, rcsGroupThread, rcsParticipant,
+ newName);
+
+ Parcel parcel = Parcel.obtain();
+ nameChangedEvent.writeToParcel(parcel, nameChangedEvent.describeContents());
+
+ parcel.setDataPosition(0);
+
+ nameChangedEvent = RcsGroupThreadNameChangedEvent.CREATOR.createFromParcel(
+ parcel);
+
+ assertThat(nameChangedEvent.getNewName()).isEqualTo(newName);
+ assertThat(nameChangedEvent.getRcsGroupThread().getThreadId()).isEqualTo(1);
+ assertThat(nameChangedEvent.getOriginatingParticipant().getId()).isEqualTo(2);
+ assertThat(nameChangedEvent.getTimestamp()).isEqualTo(1234567890);
+ }
+}
diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantJoinedEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantJoinedEventTest.java
new file mode 100644
index 0000000..d0af7db
--- /dev/null
+++ b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantJoinedEventTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.tests.ims;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.ims.RcsGroupThread;
+import android.telephony.ims.RcsGroupThreadParticipantJoinedEvent;
+import android.telephony.ims.RcsParticipant;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class RcsGroupThreadParticipantJoinedEventTest {
+
+ @Test
+ public void testCanUnparcel() {
+ RcsGroupThread rcsGroupThread = new RcsGroupThread(1);
+ RcsParticipant rcsParticipant = new RcsParticipant(2);
+
+ RcsGroupThreadParticipantJoinedEvent participantJoinedEvent =
+ new RcsGroupThreadParticipantJoinedEvent(1234567890, rcsGroupThread, rcsParticipant,
+ rcsParticipant);
+
+ Parcel parcel = Parcel.obtain();
+ participantJoinedEvent.writeToParcel(parcel, participantJoinedEvent.describeContents());
+
+ parcel.setDataPosition(0);
+
+ participantJoinedEvent = RcsGroupThreadParticipantJoinedEvent.CREATOR.createFromParcel(
+ parcel);
+
+ assertThat(participantJoinedEvent.getJoinedParticipant().getId()).isEqualTo(2);
+ assertThat(participantJoinedEvent.getRcsGroupThread().getThreadId()).isEqualTo(1);
+ assertThat(participantJoinedEvent.getOriginatingParticipant().getId()).isEqualTo(2);
+ assertThat(participantJoinedEvent.getTimestamp()).isEqualTo(1234567890);
+ }
+}
diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantLeftEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantLeftEventTest.java
new file mode 100644
index 0000000..7ba5fa6
--- /dev/null
+++ b/tests/RcsTests/src/com/android/tests/ims/RcsGroupThreadParticipantLeftEventTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.tests.ims;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.ims.RcsGroupThread;
+import android.telephony.ims.RcsGroupThreadParticipantLeftEvent;
+import android.telephony.ims.RcsParticipant;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class RcsGroupThreadParticipantLeftEventTest {
+ @Test
+ public void testCanUnparcel() {
+ RcsGroupThread rcsGroupThread = new RcsGroupThread(1);
+ RcsParticipant rcsParticipant = new RcsParticipant(2);
+
+ RcsGroupThreadParticipantLeftEvent participantLeftEvent =
+ new RcsGroupThreadParticipantLeftEvent(1234567890, rcsGroupThread, rcsParticipant,
+ rcsParticipant);
+
+ Parcel parcel = Parcel.obtain();
+ participantLeftEvent.writeToParcel(parcel, participantLeftEvent.describeContents());
+
+ parcel.setDataPosition(0);
+
+ // create from parcel
+ parcel.setDataPosition(0);
+ participantLeftEvent = RcsGroupThreadParticipantLeftEvent.CREATOR.createFromParcel(
+ parcel);
+ assertThat(participantLeftEvent.getRcsGroupThread().getThreadId()).isEqualTo(1);
+ assertThat(participantLeftEvent.getLeavingParticipantId().getId()).isEqualTo(2);
+ assertThat(participantLeftEvent.getTimestamp()).isEqualTo(1234567890);
+ }
+}
diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsMessageStoreTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsMessageStoreTest.java
deleted file mode 100644
index 44277ed..0000000
--- a/tests/RcsTests/src/com/android/tests/ims/RcsMessageStoreTest.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tests.ims;
-
-import android.support.test.runner.AndroidJUnit4;
-import android.telephony.ims.RcsMessageStore;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class RcsMessageStoreTest {
- //TODO(sahinc): Add meaningful tests once we have more of the implementation in place
- @Test
- public void testDeleteThreadDoesntCrash() {
- RcsMessageStore mRcsMessageStore = new RcsMessageStore();
- mRcsMessageStore.deleteThread(0);
- }
-}
diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsParticipantAliasChangedEventTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsParticipantAliasChangedEventTest.java
new file mode 100644
index 0000000..3e2bbbf
--- /dev/null
+++ b/tests/RcsTests/src/com/android/tests/ims/RcsParticipantAliasChangedEventTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.tests.ims;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.ims.RcsParticipant;
+import android.telephony.ims.RcsParticipantAliasChangedEvent;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class RcsParticipantAliasChangedEventTest {
+ private static final String OLD_ALIAS = "old alias";
+ private static final String NEW_ALIAS = "new alias";
+ private RcsParticipant mParticipant;
+
+ @Before
+ public void setUp() {
+ mParticipant = new RcsParticipant(3);
+ }
+
+ @Test
+ public void testCanUnparcel() {
+ RcsParticipantAliasChangedEvent aliasChangedEvent =
+ new RcsParticipantAliasChangedEvent(1234567890, mParticipant, NEW_ALIAS);
+
+ Parcel parcel = Parcel.obtain();
+ aliasChangedEvent.writeToParcel(parcel, aliasChangedEvent.describeContents());
+
+ parcel.setDataPosition(0);
+
+ aliasChangedEvent = RcsParticipantAliasChangedEvent.CREATOR.createFromParcel(
+ parcel);
+
+ assertThat(aliasChangedEvent.getParticipantId().getId()).isEqualTo(3);
+ assertThat(aliasChangedEvent.getNewAlias()).isEqualTo(NEW_ALIAS);
+ assertThat(aliasChangedEvent.getTimestamp()).isEqualTo(1234567890);
+ }
+}
diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsParticipantQueryParametersTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsParticipantQueryParametersTest.java
new file mode 100644
index 0000000..b4bcb5d
--- /dev/null
+++ b/tests/RcsTests/src/com/android/tests/ims/RcsParticipantQueryParametersTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tests.ims;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.support.test.runner.AndroidJUnit4;
+import android.telephony.ims.RcsParticipantQueryParameters;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class RcsParticipantQueryParametersTest {
+
+ @Test
+ public void testCanUnparcel() {
+ RcsParticipantQueryParameters rcsParticipantQueryParameters =
+ new RcsParticipantQueryParameters.Builder()
+ .setAliasLike("%alias_")
+ .setCanonicalAddressLike("_canonical%")
+ .setSortProperty(RcsParticipantQueryParameters.SORT_BY_CANONICAL_ADDRESS)
+ .setSortDirection(true)
+ .setResultLimit(432)
+ .build();
+
+
+ Parcel parcel = Parcel.obtain();
+ rcsParticipantQueryParameters.writeToParcel(parcel,
+ rcsParticipantQueryParameters.describeContents());
+
+ parcel.setDataPosition(0);
+ rcsParticipantQueryParameters = RcsParticipantQueryParameters.CREATOR.createFromParcel(
+ parcel);
+
+ assertThat(rcsParticipantQueryParameters.getAliasLike()).isEqualTo("%alias_");
+ assertThat(rcsParticipantQueryParameters.getCanonicalAddressLike()).contains("_canonical%");
+ assertThat(rcsParticipantQueryParameters.getLimit()).isEqualTo(432);
+ assertThat(rcsParticipantQueryParameters.getSortingProperty()).isEqualTo(
+ RcsParticipantQueryParameters.SORT_BY_CANONICAL_ADDRESS);
+ assertThat(rcsParticipantQueryParameters.getSortDirection()).isTrue();
+ }
+}
diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsParticipantTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsParticipantTest.java
deleted file mode 100644
index c402dbf..0000000
--- a/tests/RcsTests/src/com/android/tests/ims/RcsParticipantTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tests.ims;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.os.Bundle;
-import android.support.test.runner.AndroidJUnit4;
-import android.telephony.ims.RcsParticipant;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-public class RcsParticipantTest {
- private static final int ID = 123;
- private static final String ALIAS = "alias";
- private static final String CANONICAL_ADDRESS = "+1234567890";
-
- @Test
- public void testCanUnparcel() {
- RcsParticipant rcsParticipant = new RcsParticipant(ID, CANONICAL_ADDRESS);
- rcsParticipant.setAlias(ALIAS);
-
- Bundle bundle = new Bundle();
- bundle.putParcelable("Some key", rcsParticipant);
- rcsParticipant = bundle.getParcelable("Some key");
-
- assertThat(rcsParticipant.getId()).isEqualTo(ID);
- assertThat(rcsParticipant.getAlias()).isEqualTo(ALIAS);
- assertThat(rcsParticipant.getCanonicalAddress()).isEqualTo(CANONICAL_ADDRESS);
- }
-}
diff --git a/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParametersTest.java b/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParametersTest.java
index a890a38..0a70eec 100644
--- a/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParametersTest.java
+++ b/tests/RcsTests/src/com/android/tests/ims/RcsThreadQueryParametersTest.java
@@ -15,39 +15,44 @@
*/
package com.android.tests.ims;
+import static android.telephony.ims.RcsThreadQueryParameters.SORT_BY_TIMESTAMP;
+import static android.telephony.ims.RcsThreadQueryParameters.THREAD_TYPE_GROUP;
+
import static com.google.common.truth.Truth.assertThat;
-import android.os.Bundle;
+import android.os.Parcel;
import android.support.test.runner.AndroidJUnit4;
import android.telephony.ims.RcsParticipant;
import android.telephony.ims.RcsThreadQueryParameters;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.Mock;
@RunWith(AndroidJUnit4.class)
public class RcsThreadQueryParametersTest {
- private RcsThreadQueryParameters mRcsThreadQueryParameters;
- @Mock RcsParticipant mMockParticipant;
@Test
- public void testUnparceling() {
- String key = "some key";
- mRcsThreadQueryParameters = RcsThreadQueryParameters.builder()
- .isGroupThread(true)
- .withParticipant(mMockParticipant)
- .limitResultsTo(50)
- .sort(true)
+ public void testCanUnparcel() {
+ RcsParticipant rcsParticipant = new RcsParticipant(1);
+ RcsThreadQueryParameters rcsThreadQueryParameters = new RcsThreadQueryParameters.Builder()
+ .setThreadType(THREAD_TYPE_GROUP)
+ .setParticipant(rcsParticipant)
+ .setResultLimit(50)
+ .setSortProperty(SORT_BY_TIMESTAMP)
+ .setSortDirection(true)
.build();
- Bundle bundle = new Bundle();
- bundle.putParcelable(key, mRcsThreadQueryParameters);
- mRcsThreadQueryParameters = bundle.getParcelable(key);
+ Parcel parcel = Parcel.obtain();
+ rcsThreadQueryParameters.writeToParcel(parcel, rcsThreadQueryParameters.describeContents());
- assertThat(mRcsThreadQueryParameters.isGroupThread()).isTrue();
- assertThat(mRcsThreadQueryParameters.getRcsParticipants()).contains(mMockParticipant);
- assertThat(mRcsThreadQueryParameters.getLimit()).isEqualTo(50);
- assertThat(mRcsThreadQueryParameters.isAscending()).isTrue();
+ parcel.setDataPosition(0);
+ rcsThreadQueryParameters = RcsThreadQueryParameters.CREATOR.createFromParcel(parcel);
+
+ assertThat(rcsThreadQueryParameters.getThreadType()).isEqualTo(THREAD_TYPE_GROUP);
+ assertThat(rcsThreadQueryParameters.getRcsParticipantsIds())
+ .contains(rcsParticipant.getId());
+ assertThat(rcsThreadQueryParameters.getLimit()).isEqualTo(50);
+ assertThat(rcsThreadQueryParameters.getSortingProperty()).isEqualTo(SORT_BY_TIMESTAMP);
+ assertThat(rcsThreadQueryParameters.getSortDirection()).isTrue();
}
}
diff --git a/tests/net/java/android/net/LinkPropertiesTest.java b/tests/net/java/android/net/LinkPropertiesTest.java
index 299fbef..bdde096 100644
--- a/tests/net/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/java/android/net/LinkPropertiesTest.java
@@ -22,18 +22,15 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import android.net.IpPrefix;
-import android.net.LinkAddress;
-import android.net.LinkProperties;
import android.net.LinkProperties.CompareResult;
import android.net.LinkProperties.ProvisioningChange;
-import android.net.RouteInfo;
-import android.os.Parcel;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.system.OsConstants;
import android.util.ArraySet;
+import com.android.internal.util.TestUtils;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -849,18 +846,6 @@
assertEquals(new ArraySet<>(expectRemoved), (new ArraySet<>(result.removed)));
}
- private void assertParcelingIsLossless(LinkProperties source) {
- Parcel p = Parcel.obtain();
- source.writeToParcel(p, /* flags */ 0);
- p.setDataPosition(0);
- final byte[] marshalled = p.marshall();
- p = Parcel.obtain();
- p.unmarshall(marshalled, 0, marshalled.length);
- p.setDataPosition(0);
- LinkProperties dest = LinkProperties.CREATOR.createFromParcel(p);
- assertEquals(source, dest);
- }
-
@Test
public void testLinkPropertiesParcelable() throws Exception {
LinkProperties source = new LinkProperties();
@@ -882,12 +867,12 @@
source.setNat64Prefix(new IpPrefix("2001:db8:1:2:64:64::/96"));
- assertParcelingIsLossless(source);
+ TestUtils.assertParcelingIsLossless(source, LinkProperties.CREATOR);
}
@Test
public void testParcelUninitialized() throws Exception {
LinkProperties empty = new LinkProperties();
- assertParcelingIsLossless(empty);
+ TestUtils.assertParcelingIsLossless(empty, LinkProperties.CREATOR);
}
}
diff --git a/tests/net/java/android/net/TcpKeepalivePacketDataTest.java b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
new file mode 100644
index 0000000..1f2dd27
--- /dev/null
+++ b/tests/net/java/android/net/TcpKeepalivePacketDataTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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 static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.net.SocketKeepalive.InvalidPacketException;
+import android.net.TcpKeepalivePacketData.TcpSocketInfo;
+
+import com.android.internal.util.TestUtils;
+
+import libcore.net.InetAddressUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+
+@RunWith(JUnit4.class)
+public final class TcpKeepalivePacketDataTest {
+
+ @Before
+ public void setUp() {}
+
+ @Test
+ public void testV4TcpKeepalivePacket() {
+ final InetAddress srcAddr = InetAddressUtils.parseNumericAddress("192.168.0.1");
+ final InetAddress dstAddr = InetAddressUtils.parseNumericAddress("192.168.0.10");
+ final int srcPort = 1234;
+ final int dstPort = 4321;
+ final int seq = 0x11111111;
+ final int ack = 0x22222222;
+ final int wnd = 8000;
+ final int wndScale = 2;
+ TcpKeepalivePacketData resultData = null;
+ TcpSocketInfo testInfo = new TcpSocketInfo(
+ srcAddr, srcPort, dstAddr, dstPort, seq, ack, wnd, wndScale);
+ try {
+ resultData = TcpKeepalivePacketData.tcpKeepalivePacket(testInfo);
+ } catch (InvalidPacketException e) {
+ fail("InvalidPacketException: " + e);
+ }
+
+ assertEquals(testInfo.srcAddress, resultData.srcAddress);
+ assertEquals(testInfo.dstAddress, resultData.dstAddress);
+ assertEquals(testInfo.srcPort, resultData.srcPort);
+ assertEquals(testInfo.dstPort, resultData.dstPort);
+ assertEquals(testInfo.seq, resultData.tcpSeq);
+ assertEquals(testInfo.ack, resultData.tcpAck);
+ assertEquals(testInfo.rcvWndScale, resultData.tcpWndScale);
+
+ TestUtils.assertParcelingIsLossless(resultData, TcpKeepalivePacketData.CREATOR);
+
+ final byte[] packet = resultData.getPacket();
+ // IP version and TOS.
+ ByteBuffer buf = ByteBuffer.wrap(packet);
+ assertEquals(buf.getShort(), 0x4500);
+ // Source IP address.
+ byte[] ip = new byte[4];
+ buf = ByteBuffer.wrap(packet, 12, 4);
+ buf.get(ip);
+ assertArrayEquals(ip, srcAddr.getAddress());
+ // Destination IP address.
+ buf = ByteBuffer.wrap(packet, 16, 4);
+ buf.get(ip);
+ assertArrayEquals(ip, dstAddr.getAddress());
+
+ buf = ByteBuffer.wrap(packet, 20, 12);
+ // Source port.
+ assertEquals(buf.getShort(), srcPort);
+ // Destination port.
+ assertEquals(buf.getShort(), dstPort);
+ // Sequence number.
+ assertEquals(buf.getInt(), seq);
+ // Ack.
+ assertEquals(buf.getInt(), ack);
+ // Window size.
+ buf = ByteBuffer.wrap(packet, 34, 2);
+ assertEquals(buf.getShort(), wnd >> wndScale);
+ }
+
+ //TODO: add ipv6 test when ipv6 supported
+
+ @Test
+ public void testParcel() throws Exception {
+ final InetAddress srcAddr = InetAddresses.parseNumericAddress("192.168.0.1");
+ final InetAddress dstAddr = InetAddresses.parseNumericAddress("192.168.0.10");
+ final int srcPort = 1234;
+ final int dstPort = 4321;
+ final int sequence = 0x11111111;
+ final int ack = 0x22222222;
+ final int wnd = 48_000;
+ final int wndScale = 2;
+ TcpKeepalivePacketData testData = null;
+ TcpKeepalivePacketDataParcelable resultData = null;
+ TcpSocketInfo testInfo = new TcpSocketInfo(
+ srcAddr, srcPort, dstAddr, dstPort, sequence, ack, wnd, wndScale);
+ testData = TcpKeepalivePacketData.tcpKeepalivePacket(testInfo);
+ resultData = testData.toStableParcelable();
+ assertArrayEquals(resultData.srcAddress, srcAddr.getAddress());
+ assertArrayEquals(resultData.dstAddress, dstAddr.getAddress());
+ assertEquals(resultData.srcPort, srcPort);
+ assertEquals(resultData.dstPort, dstPort);
+ assertEquals(resultData.seq, sequence);
+ assertEquals(resultData.ack, ack);
+ }
+}
diff --git a/tests/net/java/com/android/internal/util/TestUtils.java b/tests/net/java/com/android/internal/util/TestUtils.java
index 6db01d3..7e5a1d3 100644
--- a/tests/net/java/com/android/internal/util/TestUtils.java
+++ b/tests/net/java/com/android/internal/util/TestUtils.java
@@ -16,12 +16,15 @@
package com.android.internal.util;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
public final class TestUtils {
private TestUtils() { }
@@ -50,4 +53,18 @@
fail(handler.toString() + " did not become idle after " + timeoutMs + " ms");
}
}
+
+ // TODO : fetch the creator through reflection or something instead of passing it
+ public static <T extends Parcelable, C extends Parcelable.Creator<T>>
+ void assertParcelingIsLossless(T source, C creator) {
+ Parcel p = Parcel.obtain();
+ source.writeToParcel(p, /* flags */ 0);
+ p.setDataPosition(0);
+ final byte[] marshalled = p.marshall();
+ p = Parcel.obtain();
+ p.unmarshall(marshalled, 0, marshalled.length);
+ p.setDataPosition(0);
+ T dest = creator.createFromParcel(p);
+ assertEquals(source, dest);
+ }
}