Add API for tethering clients change

Add a onClientsChanged callback to OnTetheringEventCallback.

The callback will provide information on connected clients combining
at least DHCP leases and WiFi AP information (WiFi AP tethering used).

Test: atest TetheringTests
Bug: 135411507
Change-Id: I7065d081c11bc606d691f76ac8b499dd075d6504
This commit is contained in:
Remi NGUYEN VAN
2020-01-20 21:26:34 +09:00
parent ecaba161bd
commit 2781c80d28
5 changed files with 351 additions and 1 deletions

View File

@@ -19,7 +19,15 @@ aidl_interface {
local_include_dir: "src",
include_dirs: ["frameworks/base/core/java"], // For framework parcelables.
srcs: [
"src/android/net/*.aidl",
// @JavaOnlyStableParcelable aidl declarations must not be listed here, as this would cause
// compilation to fail (b/148001843).
"src/android/net/IIntResultListener.aidl",
"src/android/net/ITetheringConnector.aidl",
"src/android/net/ITetheringEventCallback.aidl",
"src/android/net/TetheringCallbackStartedParcel.aidl",
"src/android/net/TetheringConfigurationParcel.aidl",
"src/android/net/TetheringRequestParcel.aidl",
"src/android/net/TetherStatesParcel.aidl",
],
backend: {
ndk: {
@@ -35,6 +43,7 @@ java_library {
name: "framework-tethering",
sdk_version: "system_current",
srcs: [
"src/android/net/TetheredClient.java",
"src/android/net/TetheringManager.java",
"src/android/net/TetheringConstants.java",
":framework-tethering-annotations",
@@ -63,6 +72,8 @@ java_library {
filegroup {
name: "framework-tethering-srcs",
srcs: [
"src/android/net/TetheredClient.aidl",
"src/android/net/TetheredClient.java",
"src/android/net/TetheringManager.java",
"src/android/net/TetheringConstants.java",
"src/android/net/IIntResultListener.aidl",

View File

@@ -0,0 +1,18 @@
/**
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net;
@JavaOnlyStableParcelable parcelable TetheredClient;

View File

@@ -0,0 +1,212 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
/**
* Information on a tethered downstream client.
* @hide
*/
@SystemApi
@TestApi
public final class TetheredClient implements Parcelable {
@NonNull
private final MacAddress mMacAddress;
@NonNull
private final List<AddressInfo> mAddresses;
// TODO: use an @IntDef here
private final int mTetheringType;
public TetheredClient(@NonNull MacAddress macAddress,
@NonNull Collection<AddressInfo> addresses, int tetheringType) {
mMacAddress = macAddress;
mAddresses = new ArrayList<>(addresses);
mTetheringType = tetheringType;
}
private TetheredClient(@NonNull Parcel in) {
this(in.readParcelable(null), in.createTypedArrayList(AddressInfo.CREATOR), in.readInt());
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeParcelable(mMacAddress, flags);
dest.writeTypedList(mAddresses);
dest.writeInt(mTetheringType);
}
@NonNull
public MacAddress getMacAddress() {
return mMacAddress;
}
@NonNull
public List<AddressInfo> getAddresses() {
return new ArrayList<>(mAddresses);
}
public int getTetheringType() {
return mTetheringType;
}
/**
* Return a new {@link TetheredClient} that has all the attributes of this instance, plus the
* {@link AddressInfo} of the provided {@link TetheredClient}.
*
* <p>Duplicate addresses are removed.
* @hide
*/
public TetheredClient addAddresses(@NonNull TetheredClient other) {
final HashSet<AddressInfo> newAddresses = new HashSet<>(
mAddresses.size() + other.mAddresses.size());
newAddresses.addAll(mAddresses);
newAddresses.addAll(other.mAddresses);
return new TetheredClient(mMacAddress, newAddresses, mTetheringType);
}
@Override
public int hashCode() {
return Objects.hash(mMacAddress, mAddresses, mTetheringType);
}
@Override
public boolean equals(@Nullable Object obj) {
if (!(obj instanceof TetheredClient)) return false;
final TetheredClient other = (TetheredClient) obj;
return mMacAddress.equals(other.mMacAddress)
&& mAddresses.equals(other.mAddresses)
&& mTetheringType == other.mTetheringType;
}
/**
* Information on an lease assigned to a tethered client.
*/
public static final class AddressInfo implements Parcelable {
@NonNull
private final LinkAddress mAddress;
@Nullable
private final String mHostname;
// TODO: use LinkAddress expiration time once it is supported
private final long mExpirationTime;
/** @hide */
public AddressInfo(@NonNull LinkAddress address, @Nullable String hostname) {
this(address, hostname, 0);
}
/** @hide */
public AddressInfo(@NonNull LinkAddress address, String hostname, long expirationTime) {
this.mAddress = address;
this.mHostname = hostname;
this.mExpirationTime = expirationTime;
}
private AddressInfo(Parcel in) {
this(in.readParcelable(null), in.readString(), in.readLong());
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeParcelable(mAddress, flags);
dest.writeString(mHostname);
dest.writeLong(mExpirationTime);
}
@NonNull
public LinkAddress getAddress() {
return mAddress;
}
@Nullable
public String getHostname() {
return mHostname;
}
/** @hide TODO: use expiration time in LinkAddress */
public long getExpirationTime() {
return mExpirationTime;
}
@Override
public int describeContents() {
return 0;
}
@Override
public int hashCode() {
return Objects.hash(mAddress, mHostname, mExpirationTime);
}
@Override
public boolean equals(@Nullable Object obj) {
if (!(obj instanceof AddressInfo)) return false;
final AddressInfo other = (AddressInfo) obj;
// Use .equals() for addresses as all changes, including address expiry changes,
// should be included.
return other.mAddress.equals(mAddress)
&& Objects.equals(mHostname, other.mHostname)
&& mExpirationTime == other.mExpirationTime;
}
@NonNull
public static final Creator<AddressInfo> CREATOR = new Creator<AddressInfo>() {
@NonNull
@Override
public AddressInfo createFromParcel(@NonNull Parcel in) {
return new AddressInfo(in);
}
@NonNull
@Override
public AddressInfo[] newArray(int size) {
return new AddressInfo[size];
}
};
}
@Override
public int describeContents() {
return 0;
}
@NonNull
public static final Creator<TetheredClient> CREATOR = new Creator<TetheredClient>() {
@NonNull
@Override
public TetheredClient createFromParcel(@NonNull Parcel in) {
return new TetheredClient(in);
}
@NonNull
@Override
public TetheredClient[] newArray(int size) {
return new TetheredClient[size];
}
};
}

View File

@@ -31,6 +31,7 @@ import android.util.ArrayMap;
import android.util.Log;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -653,6 +654,19 @@ public class TetheringManager {
* @param error One of {@code TetheringManager#TETHER_ERROR_*}.
*/
public void onError(@NonNull String ifName, int error) {}
/**
* Called when the list of tethered clients changes.
*
* <p>This callback provides best-effort information on connected clients based on state
* known to the system, however the list cannot be completely accurate (and should not be
* used for security purposes). For example, clients behind a bridge and using static IP
* assignments are not visible to the tethering device; or even when using DHCP, such
* clients may still be reported by this callback after disconnection as the system cannot
* determine if they are still connected.
* @param clients The new set of tethered clients; the collection is not ordered.
*/
public void onClientsChanged(@NonNull Collection<TetheredClient> clients) {}
}
/**

View File

@@ -0,0 +1,95 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.net
import android.net.InetAddresses.parseNumericAddress
import android.net.TetheredClient.AddressInfo
import android.net.TetheringManager.TETHERING_BLUETOOTH
import android.net.TetheringManager.TETHERING_USB
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import com.android.testutils.assertParcelSane
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
private val TEST_MACADDR = MacAddress.fromBytes(byteArrayOf(12, 23, 34, 45, 56, 67))
private val TEST_OTHER_MACADDR = MacAddress.fromBytes(byteArrayOf(23, 34, 45, 56, 67, 78))
private val TEST_ADDR1 = LinkAddress(parseNumericAddress("192.168.113.3"), 24)
private val TEST_ADDR2 = LinkAddress(parseNumericAddress("fe80::1:2:3"), 64)
private val TEST_ADDRINFO1 = AddressInfo(TEST_ADDR1, "test_hostname")
private val TEST_ADDRINFO2 = AddressInfo(TEST_ADDR2, null)
@RunWith(AndroidJUnit4::class)
@SmallTest
class TetheredClientTest {
@Test
fun testParceling() {
assertParcelSane(makeTestClient(), fieldCount = 3)
}
@Test
fun testEquals() {
assertEquals(makeTestClient(), makeTestClient())
// Different mac address
assertNotEquals(makeTestClient(), TetheredClient(
TEST_OTHER_MACADDR,
listOf(TEST_ADDRINFO1, TEST_ADDRINFO2),
TETHERING_BLUETOOTH))
// Different hostname
assertNotEquals(makeTestClient(), TetheredClient(
TEST_MACADDR,
listOf(AddressInfo(TEST_ADDR1, "test_other_hostname"), TEST_ADDRINFO2),
TETHERING_BLUETOOTH))
// Null hostname
assertNotEquals(makeTestClient(), TetheredClient(
TEST_MACADDR,
listOf(AddressInfo(TEST_ADDR1, null), TEST_ADDRINFO2),
TETHERING_BLUETOOTH))
// Missing address
assertNotEquals(makeTestClient(), TetheredClient(
TEST_MACADDR,
listOf(TEST_ADDRINFO2),
TETHERING_BLUETOOTH))
// Different type
assertNotEquals(makeTestClient(), TetheredClient(
TEST_MACADDR,
listOf(TEST_ADDRINFO1, TEST_ADDRINFO2),
TETHERING_BLUETOOTH))
}
@Test
fun testAddAddresses() {
val client1 = TetheredClient(TEST_MACADDR, listOf(TEST_ADDRINFO1), TETHERING_USB)
val client2 = TetheredClient(TEST_OTHER_MACADDR, listOf(TEST_ADDRINFO2), TETHERING_USB)
assertEquals(TetheredClient(
TEST_MACADDR,
listOf(TEST_ADDRINFO1, TEST_ADDRINFO2),
TETHERING_USB), client1.addAddresses(client2))
}
private fun makeTestClient() = TetheredClient(
TEST_MACADDR,
listOf(TEST_ADDRINFO1, TEST_ADDRINFO2),
TETHERING_BLUETOOTH)
}