diff --git a/Tethering/apex/Android.bp b/Tethering/apex/Android.bp index cf9b359952..d3b01ea1f0 100644 --- a/Tethering/apex/Android.bp +++ b/Tethering/apex/Android.bp @@ -203,6 +203,8 @@ bootclasspath_fragment { // result in a build failure due to inconsistent flags. package_prefixes: [ "android.nearby.aidl", + "android.remoteauth.aidl", + "android.remoteauth", "android.net.apf", "android.net.connectivity", "android.net.http.apihelpers", diff --git a/framework-t/Android.bp b/framework-t/Android.bp index 05b84c2869..dacdaf2b30 100644 --- a/framework-t/Android.bp +++ b/framework-t/Android.bp @@ -43,6 +43,7 @@ java_defaults { ":framework-connectivity-tiramisu-updatable-sources", ":framework-nearby-java-sources", ":framework-thread-sources", + ":framework-remoteauth-java-sources", ], libs: [ "unsupportedappusage", @@ -130,8 +131,10 @@ java_sdk_library { "android.net", "android.net.nsd", "android.nearby", + "android.remoteauth", "com.android.connectivity", "com.android.nearby", + "com.android.remoteauth", ], hidden_api: { @@ -153,6 +156,7 @@ java_sdk_library { "//packages/modules/Connectivity/service", // For R8 only "//packages/modules/Connectivity/service-t", "//packages/modules/Connectivity/nearby:__subpackages__", + "//packages/modules/Connectivity/remoteauth:__subpackages__", "//frameworks/base", // Tests using hidden APIs diff --git a/framework-t/api/module-lib-current.txt b/framework-t/api/module-lib-current.txt index 5a8d47b5f5..42c83d84c7 100644 --- a/framework-t/api/module-lib-current.txt +++ b/framework-t/api/module-lib-current.txt @@ -207,3 +207,43 @@ package android.net { } +package android.remoteauth { + + public interface DeviceDiscoveryCallback { + method public void onDeviceUpdate(@NonNull android.remoteauth.RemoteDevice, int); + method public void onTimeout(); + field public static final int STATE_LOST = 0; // 0x0 + field public static final int STATE_SEEN = 1; // 0x1 + } + + public final class RemoteAuthFrameworkInitializer { + method public static void registerServiceWrappers(); + } + + public class RemoteAuthManager { + method public boolean isRemoteAuthSupported(); + method public boolean startDiscovery(int, @NonNull java.util.concurrent.Executor, @NonNull android.remoteauth.DeviceDiscoveryCallback); + method public void stopDiscovery(@NonNull android.remoteauth.DeviceDiscoveryCallback); + } + + public final class RemoteDevice implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public int getConnectionId(); + method @Nullable public String getName(); + method public int getRegistrationState(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final int STATE_NOT_REGISTERED = 0; // 0x0 + field public static final int STATE_REGISTERED = 1; // 0x1 + } + + public static final class RemoteDevice.Builder { + ctor public RemoteDevice.Builder(int); + method @NonNull public android.remoteauth.RemoteDevice build(); + method @NonNull public android.remoteauth.RemoteDevice.Builder setConnectionId(int); + method @NonNull public android.remoteauth.RemoteDevice.Builder setName(@Nullable String); + method @NonNull public android.remoteauth.RemoteDevice.Builder setRegistrationState(int); + } + +} + diff --git a/framework/Android.bp b/framework/Android.bp index 8de8097756..813e296377 100644 --- a/framework/Android.bp +++ b/framework/Android.bp @@ -62,7 +62,6 @@ java_defaults { ":framework-connectivity-sources", ":net-utils-framework-common-srcs", ":framework-connectivity-api-shared-srcs", - ":framework-remoteauth-java-sources", ], aidl: { generate_get_transaction_name: true, @@ -153,7 +152,6 @@ java_sdk_library { "//packages/modules/Connectivity/framework-t", "//packages/modules/Connectivity/service", "//packages/modules/Connectivity/service-t", - "//packages/modules/Connectivity/remoteauth:__subpackages__", "//frameworks/base/packages/Connectivity/service", "//frameworks/base", diff --git a/remoteauth/TEST_MAPPING b/remoteauth/TEST_MAPPING index 5ad8da6157..5061319ba8 100644 --- a/remoteauth/TEST_MAPPING +++ b/remoteauth/TEST_MAPPING @@ -7,7 +7,7 @@ // TODO(b/193602229): uncomment once it's supported. //"mainline-presubmit": [ // { - // "name": "RemoteAuthUnitTests[com.google.android.tethering.apex]" + // "name": "RemoteAuthUnitTests[com.google.android.remoteauth.apex]" // } //] } diff --git a/remoteauth/framework/Android.bp b/remoteauth/framework/Android.bp index 48d10b6fda..71b621a3ec 100644 --- a/remoteauth/framework/Android.bp +++ b/remoteauth/framework/Android.bp @@ -25,7 +25,7 @@ filegroup { ], path: "java", visibility: [ - "//packages/modules/Connectivity/framework:__subpackages__", + "//packages/modules/Connectivity/framework-t:__subpackages__", ], } @@ -43,7 +43,13 @@ java_library { name: "framework-remoteauth-static", srcs: [":framework-remoteauth-java-sources"], sdk_version: "module_current", - libs: [], - static_libs: [], + libs: [ + "androidx.annotation_annotation", + "framework-annotations-lib", + "framework-bluetooth", + ], + static_libs: [ + "modules-utils-preconditions", + ], visibility: ["//packages/modules/Connectivity/remoteauth/tests:__subpackages__"], } diff --git a/remoteauth/framework/java/android/remoteauth/DeviceDiscoveryCallback.java b/remoteauth/framework/java/android/remoteauth/DeviceDiscoveryCallback.java new file mode 100644 index 0000000000..f53e2dc3a0 --- /dev/null +++ b/remoteauth/framework/java/android/remoteauth/DeviceDiscoveryCallback.java @@ -0,0 +1,55 @@ +/* + * Copyright 2023 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.remoteauth; + +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + +import android.annotation.NonNull; +import android.annotation.SystemApi; + +import androidx.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Reports newly discovered remote devices. + * + * @hide + */ +@SystemApi(client = MODULE_LIBRARIES) +public interface DeviceDiscoveryCallback { + /** The device is no longer seen in the discovery process. */ + int STATE_LOST = 0; + /** The device is seen in the discovery process */ + int STATE_SEEN = 1; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({STATE_LOST, STATE_SEEN}) + @interface State {} + + /** + * Invoked for every change in remote device state. + * + * @param device remote device + * @param state indicates if found or lost + */ + void onDeviceUpdate(@NonNull RemoteDevice device, @State int state); + + /** Invoked when discovery is stopped due to timeout. */ + void onTimeout(); +} diff --git a/remoteauth/framework/java/android/remoteauth/IDeviceDiscoveryListener.aidl b/remoteauth/framework/java/android/remoteauth/IDeviceDiscoveryListener.aidl new file mode 100644 index 0000000000..2ad6a6a0d1 --- /dev/null +++ b/remoteauth/framework/java/android/remoteauth/IDeviceDiscoveryListener.aidl @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 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.remoteauth; + +import android.remoteauth.RemoteDevice; + +/** + * Binder callback for DeviceDiscoveryCallback. + * + * {@hide} + */ +oneway interface IDeviceDiscoveryListener { + /** Reports a {@link RemoteDevice} being discovered. */ + void onDiscovered(in RemoteDevice remoteDevice); + + /** Reports a {@link RemoteDevice} is no longer within range. */ + void onLost(in RemoteDevice remoteDevice); + + /** Reports a timeout of {@link RemoteDevice} was reached. */ + void onTimeout(); +} diff --git a/remoteauth/framework/java/android/remoteauth/IRemoteAuthService.aidl b/remoteauth/framework/java/android/remoteauth/IRemoteAuthService.aidl new file mode 100644 index 0000000000..f4387e396f --- /dev/null +++ b/remoteauth/framework/java/android/remoteauth/IRemoteAuthService.aidl @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2023 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.remoteauth; + +import android.remoteauth.IDeviceDiscoveryListener; + +/** + * Interface for communicating with the RemoteAuthService. + * These methods are all require MANAGE_REMOTE_AUTH signature permission. + * @hide + */ +interface IRemoteAuthService { + // This is protected by the MANAGE_REMOTE_AUTH signature permission. + boolean isRemoteAuthSupported(); + + // This is protected by the MANAGE_REMOTE_AUTH signature permission. + boolean registerDiscoveryListener(in IDeviceDiscoveryListener deviceDiscoveryListener, + int userId, + int timeoutMs, + String packageName, + @nullable String attributionTag); + + // This is protected by the MANAGE_REMOTE_AUTH signature permission. + void unregisterDiscoveryListener(in IDeviceDiscoveryListener deviceDiscoveryListener, + int userId, + String packageName, + @nullable String attributionTag); +} \ No newline at end of file diff --git a/remoteauth/framework/java/android/remoteauth/RemoteAuthFrameworkInitializer.java b/remoteauth/framework/java/android/remoteauth/RemoteAuthFrameworkInitializer.java new file mode 100644 index 0000000000..dfd7726ab6 --- /dev/null +++ b/remoteauth/framework/java/android/remoteauth/RemoteAuthFrameworkInitializer.java @@ -0,0 +1,50 @@ +/* + * Copyright 2023 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.remoteauth; + +import android.annotation.SystemApi; +import android.app.SystemServiceRegistry; +import android.content.Context; + +/** + * Class for initializing RemoteAuth service. + * + * @hide + */ +@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) +public final class RemoteAuthFrameworkInitializer { + private RemoteAuthFrameworkInitializer() {} + + /** + * Called by {@link SystemServiceRegistry}'s static initializer and registers all Nearby + * services to {@link Context}, so that {@link Context#getSystemService} can return them. + * + * @throws IllegalStateException if this is called from anywhere besides {@link + * SystemServiceRegistry} + */ + public static void registerServiceWrappers() { + // TODO(b/290092977): Change to Context.REMOTE_AUTH_SERVICE after aosp/2681375 + // is automerges from aosp-main to udc-mainline-prod + SystemServiceRegistry.registerContextAwareService( + RemoteAuthManager.REMOTE_AUTH_SERVICE, + RemoteAuthManager.class, + (context, serviceBinder) -> { + IRemoteAuthService service = IRemoteAuthService.Stub.asInterface(serviceBinder); + return new RemoteAuthManager(context, service); + }); + } +} diff --git a/remoteauth/framework/java/android/remoteauth/RemoteAuthManager.java b/remoteauth/framework/java/android/remoteauth/RemoteAuthManager.java new file mode 100644 index 0000000000..c025a55f37 --- /dev/null +++ b/remoteauth/framework/java/android/remoteauth/RemoteAuthManager.java @@ -0,0 +1,240 @@ +/* + * Copyright 2023 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.remoteauth; + +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; +import static android.remoteauth.DeviceDiscoveryCallback.STATE_LOST; +import static android.remoteauth.DeviceDiscoveryCallback.STATE_SEEN; + +import android.annotation.CallbackExecutor; +import android.annotation.NonNull; +import android.annotation.SuppressLint; +import android.annotation.SystemApi; +import android.annotation.SystemService; +import android.annotation.UserIdInt; +import android.content.Context; +import android.os.RemoteException; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.Preconditions; + +import java.lang.ref.WeakReference; +import java.util.Objects; +import java.util.WeakHashMap; +import java.util.concurrent.Executor; + +/** + * A system service providing a way to perform remote authentication-related operations such as + * discovering, registering and authenticating via remote authenticator. + * + *

To get a {@link RemoteAuthManager} instance, call the + * Context.getSystemService(Context.REMOTE_AUTH_SERVICE). + * + * @hide + */ +@SystemApi(client = MODULE_LIBRARIES) +// TODO(b/290092977): Change to Context.REMOTE_AUTH_SERVICE after aosp/2681375 +// is automerges from aosp-main to udc-mainline-prod +@SystemService(RemoteAuthManager.REMOTE_AUTH_SERVICE) +public class RemoteAuthManager { + private static final String TAG = "RemoteAuthManager"; + + /** @hide */ + public static final String REMOTE_AUTH_SERVICE = "remote_auth"; + + private final Context mContext; + private final IRemoteAuthService mService; + + @GuardedBy("mDiscoveryListeners") + private final WeakHashMap< + DeviceDiscoveryCallback, WeakReference> + mDiscoveryListeners = new WeakHashMap<>(); + + /** @hide */ + public RemoteAuthManager(@NonNull Context context, @NonNull IRemoteAuthService service) { + Objects.requireNonNull(context); + Objects.requireNonNull(service); + mContext = context; + mService = service; + } + + /** + * Returns if this device can be enrolled in the feature. + * + * @return true if this device can be enrolled + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + // TODO(b/297301535): @RequiresPermission(MANAGE_REMOTE_AUTH) + public boolean isRemoteAuthSupported() { + try { + return mService.isRemoteAuthSupported(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Starts remote authenticator discovery process with timeout. Devices that are capable to + * operate as remote authenticators are reported via callback. The discovery stops by calling + * stopDiscovery or after a timeout. + * + * @param timeoutMs the duration in milliseconds after which discovery will stop automatically + * @param executor the callback will be executed in the executor thread + * @param callback to be used by the caller to get notifications about remote devices + * @return {@code true} if discovery began successfully, {@code false} otherwise + * @hide + */ + @SystemApi(client = MODULE_LIBRARIES) + // TODO(b/297301535): @RequiresPermission(MANAGE_REMOTE_AUTH) + public boolean startDiscovery( + int timeoutMs, + @CallbackExecutor @NonNull Executor executor, + @NonNull DeviceDiscoveryCallback callback) { + try { + Preconditions.checkNotNull(callback, "invalid null callback"); + Preconditions.checkArgument(timeoutMs > 0, "invalid timeoutMs, must be > 0"); + Preconditions.checkNotNull(executor, "invalid null executor"); + DeviceDiscoveryListenerTransport transport; + synchronized (mDiscoveryListeners) { + WeakReference reference = + mDiscoveryListeners.get(callback); + transport = (reference != null) ? reference.get() : null; + if (transport == null) { + transport = + new DeviceDiscoveryListenerTransport( + callback, mContext.getUser().getIdentifier(), executor); + } + + boolean result = + mService.registerDiscoveryListener( + transport, + mContext.getUser().getIdentifier(), + timeoutMs, + mContext.getPackageName(), + mContext.getAttributionTag()); + if (result) { + mDiscoveryListeners.put(callback, new WeakReference<>(transport)); + return true; + } + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + return false; + } + + /** + * Removes this listener from device discovery notifications. The given callback is guaranteed + * not to receive any invocations that happen after this method is invoked. + * + * @param callback the callback for the previously started discovery to be ended + * @hide + */ + // Suppressed lint: Registration methods should have overload that accepts delivery Executor. + // Already have executor in startDiscovery() method. + @SuppressLint("ExecutorRegistration") + @SystemApi(client = MODULE_LIBRARIES) + // TODO(b/297301535): @RequiresPermission(MANAGE_REMOTE_AUTH) + public void stopDiscovery(@NonNull DeviceDiscoveryCallback callback) { + Preconditions.checkNotNull(callback, "invalid null scanCallback"); + try { + DeviceDiscoveryListenerTransport transport; + synchronized (mDiscoveryListeners) { + WeakReference reference = + mDiscoveryListeners.remove(callback); + transport = (reference != null) ? reference.get() : null; + } + if (transport != null) { + mService.unregisterDiscoveryListener( + transport, + transport.getUserId(), + mContext.getPackageName(), + mContext.getAttributionTag()); + } else { + Log.d( + TAG, + "Cannot stop discovery with this callback " + + "because it is not registered."); + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private class DeviceDiscoveryListenerTransport extends IDeviceDiscoveryListener.Stub { + + private volatile @NonNull DeviceDiscoveryCallback mDeviceDiscoveryCallback; + private Executor mExecutor; + private @UserIdInt int mUserId; + + DeviceDiscoveryListenerTransport( + DeviceDiscoveryCallback deviceDiscoveryCallback, + @UserIdInt int userId, + @CallbackExecutor Executor executor) { + Preconditions.checkNotNull(deviceDiscoveryCallback, "invalid null callback"); + mDeviceDiscoveryCallback = deviceDiscoveryCallback; + mUserId = userId; + mExecutor = executor; + } + + @UserIdInt + int getUserId() { + return mUserId; + } + + @Override + public void onDiscovered(RemoteDevice remoteDevice) throws RemoteException { + if (remoteDevice == null) { + Log.w(TAG, "onDiscovered is called with null device"); + return; + } + Log.i(TAG, "Notifying the caller about discovered: " + remoteDevice); + mExecutor.execute( + () -> { + mDeviceDiscoveryCallback.onDeviceUpdate(remoteDevice, STATE_SEEN); + }); + } + + @Override + public void onLost(RemoteDevice remoteDevice) throws RemoteException { + if (remoteDevice == null) { + Log.w(TAG, "onLost is called with null device"); + return; + } + Log.i(TAG, "Notifying the caller about lost: " + remoteDevice); + mExecutor.execute( + () -> { + mDeviceDiscoveryCallback.onDeviceUpdate(remoteDevice, STATE_LOST); + }); + } + + @Override + public void onTimeout() { + Log.i(TAG, "Notifying the caller about discovery timeout"); + mExecutor.execute( + () -> { + mDeviceDiscoveryCallback.onTimeout(); + }); + synchronized (mDiscoveryListeners) { + mDiscoveryListeners.remove(mDeviceDiscoveryCallback); + } + mDeviceDiscoveryCallback = null; + } + } +} diff --git a/remoteauth/framework/java/android/remoteauth/RemoteDevice.aidl b/remoteauth/framework/java/android/remoteauth/RemoteDevice.aidl new file mode 100644 index 0000000000..ea38be2a17 --- /dev/null +++ b/remoteauth/framework/java/android/remoteauth/RemoteDevice.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2023 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.remoteauth; + +parcelable RemoteDevice; \ No newline at end of file diff --git a/remoteauth/framework/java/android/remoteauth/RemoteDevice.java b/remoteauth/framework/java/android/remoteauth/RemoteDevice.java new file mode 100644 index 0000000000..4cd23991f9 --- /dev/null +++ b/remoteauth/framework/java/android/remoteauth/RemoteDevice.java @@ -0,0 +1,203 @@ +/* + * Copyright 2023 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.remoteauth; + +import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SystemApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * Remote device that can be registered as remote authenticator. + * + * @hide + */ +// TODO(b/295407748) Change to use @DataClass +@SystemApi(client = MODULE_LIBRARIES) +public final class RemoteDevice implements Parcelable { + /** The remote device is not registered as remote authenticator. */ + public static final int STATE_NOT_REGISTERED = 0; + /** The remote device is registered as remote authenticator. */ + public static final int STATE_REGISTERED = 1; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({STATE_NOT_REGISTERED, STATE_REGISTERED}) + @interface RegistrationState {} + + @NonNull private final String mName; + private final @RegistrationState int mRegistrationState; + private final int mConnectionId; + + public static final @NonNull Creator CREATOR = + new Creator<>() { + @Override + public RemoteDevice createFromParcel(Parcel in) { + RemoteDevice.Builder builder = new RemoteDevice.Builder(); + builder.setName(in.readString()); + builder.setRegistrationState(in.readInt()); + builder.setConnectionId(in.readInt()); + + return builder.build(); + } + + @Override + public RemoteDevice[] newArray(int size) { + return new RemoteDevice[size]; + } + }; + + private RemoteDevice( + @Nullable String name, + @RegistrationState int registrationState, + @NonNull int connectionId) { + this.mName = name; + this.mRegistrationState = registrationState; + this.mConnectionId = connectionId; + } + + /** Gets the name of the {@link RemoteDevice} device. */ + @Nullable + public String getName() { + return mName; + } + + /** Returns registration state of the {@link RemoteDevice}. */ + public @RegistrationState int getRegistrationState() { + return mRegistrationState; + } + + /** Returns connection id of the {@link RemoteDevice}. */ + @NonNull + public int getConnectionId() { + return mConnectionId; + } + + @Override + public int describeContents() { + return 0; + } + + /** Returns a string representation of {@link RemoteDevice}. */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("RemoteDevice ["); + sb.append("name=").append(mName).append(", "); + sb.append("registered=").append(mRegistrationState).append(", "); + sb.append("connectionId=").append(mConnectionId); + sb.append("]"); + return sb.toString(); + } + + /** Returns true if this {@link RemoteDevice} object is equals to other. */ + @Override + public boolean equals(Object other) { + if (other instanceof RemoteDevice) { + RemoteDevice otherDevice = (RemoteDevice) other; + return Objects.equals(this.mName, otherDevice.mName) + && this.getRegistrationState() == otherDevice.getRegistrationState() + && this.mConnectionId == otherDevice.mConnectionId; + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(mName, mRegistrationState, mConnectionId); + } + + /** + * Helper function for writing {@link RemoteDevice} to a Parcel. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written. + */ + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + String name = getName(); + dest.writeString(name); + dest.writeInt(getRegistrationState()); + dest.writeInt(getConnectionId()); + } + + /** Builder for {@link RemoteDevice} objects. */ + public static final class Builder { + @Nullable private String mName; + // represents if device is already registered + private @RegistrationState int mRegistrationState; + private int mConnectionId; + + private Builder() { + } + + public Builder(final int connectionId) { + this.mConnectionId = connectionId; + } + + /** + * Sets the name of the {@link RemoteDevice} device. + * + * @param name of the {@link RemoteDevice}. Can be {@code null} if there is no name. + */ + @NonNull + public RemoteDevice.Builder setName(@Nullable String name) { + this.mName = name; + return this; + } + + /** + * Sets the registration state of the {@link RemoteDevice} device. + * + * @param registrationState of the {@link RemoteDevice}. + */ + @NonNull + public RemoteDevice.Builder setRegistrationState(@RegistrationState int registrationState) { + this.mRegistrationState = registrationState; + return this; + } + + /** + * Sets the connectionInfo of the {@link RemoteDevice} device. + * + * @param connectionId of the RemoteDevice. + */ + @NonNull + public RemoteDevice.Builder setConnectionId(int connectionId) { + this.mConnectionId = connectionId; + return this; + } + + /** + * Creates the {@link RemoteDevice} instance. + * + * @return the configured {@link RemoteDevice} instance. + */ + @NonNull + public RemoteDevice build() { + return new RemoteDevice(mName, mRegistrationState, mConnectionId); + } + } +} diff --git a/remoteauth/service/Android.bp b/remoteauth/service/Android.bp index 5c5a2fbc3c..c3a9fb3045 100644 --- a/remoteauth/service/Android.bp +++ b/remoteauth/service/Android.bp @@ -29,8 +29,24 @@ java_library { defaults: [ "framework-system-server-module-defaults" ], - libs: [], - static_libs: [], + libs: [ + "androidx.annotation_annotation", + "framework-bluetooth", + "error_prone_annotations", + "framework-configinfrastructure", + "framework-connectivity-pre-jarjar", + "framework-connectivity-t-pre-jarjar", + "framework-statsd", + ], + static_libs: [ + "libprotobuf-java-lite", + "fast-pair-lite-protos", + "modules-utils-build", + "modules-utils-handlerexecutor", + "modules-utils-preconditions", + "modules-utils-backgroundthread", + "presence-lite-protos", + ], sdk_version: "system_server_current", // This is included in service-connectivity which is 30+ // TODO (b/293613362): allow APEXes to have service jars with higher min_sdk than the APEX diff --git a/remoteauth/service/java/com/android/server/remoteauth/RemoteAuthService.java b/remoteauth/service/java/com/android/server/remoteauth/RemoteAuthService.java new file mode 100644 index 0000000000..41ce89aaf9 --- /dev/null +++ b/remoteauth/service/java/com/android/server/remoteauth/RemoteAuthService.java @@ -0,0 +1,70 @@ +/* + * Copyright 2023 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.remoteauth; + +import android.annotation.Nullable; +import android.annotation.UserIdInt; +import android.content.Context; +import android.remoteauth.IDeviceDiscoveryListener; +import android.remoteauth.IRemoteAuthService; + +import com.android.internal.util.Preconditions; + +/** Service implementing remoteauth functionality. */ +public class RemoteAuthService extends IRemoteAuthService.Stub { + public static final String TAG = "RemoteAuthService"; + + public RemoteAuthService(Context context) { + Preconditions.checkNotNull(context); + // TODO(b/290280702): Create here RemoteConnectivityManager and RangingManager + } + + @Override + public boolean isRemoteAuthSupported() { + // TODO(b/297301535): checkPermission(mContext, MANAGE_REMOTE_AUTH); + // TODO(b/290676192): integrate with RangingManager + // (check if UWB is supported by this device) + return true; + } + + @Override + public boolean registerDiscoveryListener( + IDeviceDiscoveryListener deviceDiscoveryListener, + @UserIdInt int userId, + int timeoutMs, + String packageName, + @Nullable String attributionTag) { + // TODO(b/297301535): checkPermission(mContext, MANAGE_REMOTE_AUTH); + // TODO(b/290280702): implement register discovery logic + return true; + } + + @Override + public void unregisterDiscoveryListener( + IDeviceDiscoveryListener deviceDiscoveryListener, + @UserIdInt int userId, + String packageName, + @Nullable String attributionTag) { + // TODO(b/297301535): checkPermission(mContext, MANAGE_REMOTE_AUTH); + // TODO(b/290094221): implement unregister logic + } + + private static void checkPermission(Context context, String permission) { + context.enforceCallingOrSelfPermission(permission, + "Must have " + permission + " permission."); + } +} diff --git a/remoteauth/tests/unit/Android.bp b/remoteauth/tests/unit/Android.bp index 8c08a1b4f8..4b92d847eb 100644 --- a/remoteauth/tests/unit/Android.bp +++ b/remoteauth/tests/unit/Android.bp @@ -37,6 +37,7 @@ android_test { "androidx.test.rules", "framework-remoteauth-static", "junit", + "libprotobuf-java-lite", "platform-test-annotations", "service-remoteauth-pre-jarjar", "truth-prebuilt", diff --git a/remoteauth/tests/unit/src/android/remoteauth/RemoteAuthManagerTest.java b/remoteauth/tests/unit/src/android/remoteauth/RemoteAuthManagerTest.java index 5cf3e6b15b..6b43355ea7 100644 --- a/remoteauth/tests/unit/src/android/remoteauth/RemoteAuthManagerTest.java +++ b/remoteauth/tests/unit/src/android/remoteauth/RemoteAuthManagerTest.java @@ -23,6 +23,7 @@ import android.platform.test.annotations.Presubmit; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -31,6 +32,9 @@ import org.junit.runner.RunWith; @SmallTest @RunWith(AndroidJUnit4.class) public class RemoteAuthManagerTest { + @Before + public void setUp() {} + @Test public void testStub() { assertTrue(true); diff --git a/service-t/Android.bp b/service-t/Android.bp index ce5bb92f4a..83caf35538 100644 --- a/service-t/Android.bp +++ b/service-t/Android.bp @@ -56,6 +56,7 @@ java_library { "service-connectivity-pre-jarjar", "service-nearby-pre-jarjar", "service-thread-pre-jarjar", + "service-remoteauth-pre-jarjar", "ServiceConnectivityResources", "unsupportedappusage", ], diff --git a/service-t/src/com/android/server/ConnectivityServiceInitializer.java b/service-t/src/com/android/server/ConnectivityServiceInitializer.java index 626c2eb68d..2da067a93a 100644 --- a/service-t/src/com/android/server/ConnectivityServiceInitializer.java +++ b/service-t/src/com/android/server/ConnectivityServiceInitializer.java @@ -17,6 +17,7 @@ package com.android.server; import android.content.Context; +import android.remoteauth.RemoteAuthManager; import android.util.Log; import com.android.modules.utils.build.SdkLevel; @@ -25,6 +26,7 @@ import com.android.server.connectivity.ConnectivityNativeService; import com.android.server.ethernet.EthernetService; import com.android.server.ethernet.EthernetServiceImpl; import com.android.server.nearby.NearbyService; +import com.android.server.remoteauth.RemoteAuthService; /** * Connectivity service initializer for core networking. This is called by system server to create @@ -38,6 +40,7 @@ public final class ConnectivityServiceInitializer extends SystemService { private final NsdService mNsdService; private final NearbyService mNearbyService; private final EthernetServiceImpl mEthernetServiceImpl; + private final RemoteAuthService mRemoteAuthService; public ConnectivityServiceInitializer(Context context) { super(context); @@ -49,6 +52,7 @@ public final class ConnectivityServiceInitializer extends SystemService { mConnectivityNative = createConnectivityNativeService(context); mNsdService = createNsdService(context); mNearbyService = createNearbyService(context); + mRemoteAuthService = createRemoteAuthService(context); } @Override @@ -85,6 +89,11 @@ public final class ConnectivityServiceInitializer extends SystemService { /* allowIsolated= */ false); } + if (mRemoteAuthService != null) { + Log.i(TAG, "Registering " + RemoteAuthManager.REMOTE_AUTH_SERVICE); + publishBinderService(RemoteAuthManager.REMOTE_AUTH_SERVICE, mRemoteAuthService, + /* allowIsolated= */ false); + } } @Override @@ -140,6 +149,20 @@ public final class ConnectivityServiceInitializer extends SystemService { } } + /** Return RemoteAuth service instance */ + private RemoteAuthService createRemoteAuthService(final Context context) { + if (!SdkLevel.isAtLeastV()) return null; + try { + return new RemoteAuthService(context); + } catch (UnsupportedOperationException e) { + // RemoteAuth is not yet supported in all branches + // TODO: remove catch clause when it is available. + Log.i(TAG, "Skipping unsupported service " + + RemoteAuthManager.REMOTE_AUTH_SERVICE); + return null; + } + } + /** * Return EthernetServiceImpl instance or null if current SDK is lower than T or Ethernet * service isn't necessary.