1 /* 2 * Copyright 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.remoteauth; 18 19 import static android.remoteauth.DeviceDiscoveryCallback.STATE_LOST; 20 import static android.remoteauth.DeviceDiscoveryCallback.STATE_SEEN; 21 22 import android.annotation.CallbackExecutor; 23 import android.annotation.NonNull; 24 import android.annotation.SuppressLint; 25 import android.annotation.SystemService; 26 import android.annotation.UserIdInt; 27 import android.content.Context; 28 import android.os.RemoteException; 29 import android.util.Log; 30 31 import com.android.internal.annotations.GuardedBy; 32 import com.android.internal.util.Preconditions; 33 34 import java.lang.ref.WeakReference; 35 import java.util.Objects; 36 import java.util.WeakHashMap; 37 import java.util.concurrent.Executor; 38 39 /** 40 * A system service providing a way to perform remote authentication-related operations such as 41 * discovering, registering and authenticating via remote authenticator. 42 * 43 * <p>To get a {@link RemoteAuthManager} instance, call the <code> 44 * Context.getSystemService(Context.REMOTE_AUTH_SERVICE)</code>. 45 * 46 * @hide 47 */ 48 // TODO(b/290092977): Add back after M-2023-11 release - @SystemApi(client = MODULE_LIBRARIES) 49 // TODO(b/290092977): Change to Context.REMOTE_AUTH_SERVICE after aosp/2681375 50 // is automerges from aosp-main to udc-mainline-prod 51 @SystemService(RemoteAuthManager.REMOTE_AUTH_SERVICE) 52 public class RemoteAuthManager { 53 private static final String TAG = "RemoteAuthManager"; 54 55 /** @hide */ 56 public static final String REMOTE_AUTH_SERVICE = "remote_auth"; 57 58 private final Context mContext; 59 private final IRemoteAuthService mService; 60 61 @GuardedBy("mDiscoveryListeners") 62 private final WeakHashMap< 63 DeviceDiscoveryCallback, WeakReference<DeviceDiscoveryListenerTransport>> 64 mDiscoveryListeners = new WeakHashMap<>(); 65 66 /** @hide */ RemoteAuthManager(@onNull Context context, @NonNull IRemoteAuthService service)67 public RemoteAuthManager(@NonNull Context context, @NonNull IRemoteAuthService service) { 68 Objects.requireNonNull(context); 69 Objects.requireNonNull(service); 70 mContext = context; 71 mService = service; 72 } 73 74 /** 75 * Returns if this device can be enrolled in the feature. 76 * 77 * @return true if this device can be enrolled 78 * @hide 79 */ 80 // TODO(b/290092977): Add back after M-2023-11 release - @SystemApi(client = MODULE_LIBRARIES) 81 // TODO(b/297301535): @RequiresPermission(MANAGE_REMOTE_AUTH) isRemoteAuthSupported()82 public boolean isRemoteAuthSupported() { 83 try { 84 return mService.isRemoteAuthSupported(); 85 } catch (RemoteException e) { 86 throw e.rethrowFromSystemServer(); 87 } 88 } 89 90 /** 91 * Starts remote authenticator discovery process with timeout. Devices that are capable to 92 * operate as remote authenticators are reported via callback. The discovery stops by calling 93 * stopDiscovery or after a timeout. 94 * 95 * @param timeoutMs the duration in milliseconds after which discovery will stop automatically 96 * @param executor the callback will be executed in the executor thread 97 * @param callback to be used by the caller to get notifications about remote devices 98 * @return {@code true} if discovery began successfully, {@code false} otherwise 99 * @hide 100 */ 101 // TODO(b/290092977): Add back after M-2023-11 release - @SystemApi(client = MODULE_LIBRARIES) 102 // TODO(b/297301535): @RequiresPermission(MANAGE_REMOTE_AUTH) startDiscovery( int timeoutMs, @CallbackExecutor @NonNull Executor executor, @NonNull DeviceDiscoveryCallback callback)103 public boolean startDiscovery( 104 int timeoutMs, 105 @CallbackExecutor @NonNull Executor executor, 106 @NonNull DeviceDiscoveryCallback callback) { 107 try { 108 Preconditions.checkNotNull(callback, "invalid null callback"); 109 Preconditions.checkArgument(timeoutMs > 0, "invalid timeoutMs, must be > 0"); 110 Preconditions.checkNotNull(executor, "invalid null executor"); 111 DeviceDiscoveryListenerTransport transport; 112 synchronized (mDiscoveryListeners) { 113 WeakReference<DeviceDiscoveryListenerTransport> reference = 114 mDiscoveryListeners.get(callback); 115 transport = (reference != null) ? reference.get() : null; 116 if (transport == null) { 117 transport = 118 new DeviceDiscoveryListenerTransport( 119 callback, mContext.getUser().getIdentifier(), executor); 120 } 121 122 boolean result = 123 mService.registerDiscoveryListener( 124 transport, 125 mContext.getUser().getIdentifier(), 126 timeoutMs, 127 mContext.getPackageName(), 128 mContext.getAttributionTag()); 129 if (result) { 130 mDiscoveryListeners.put(callback, new WeakReference<>(transport)); 131 return true; 132 } 133 } 134 } catch (RemoteException e) { 135 throw e.rethrowFromSystemServer(); 136 } 137 return false; 138 } 139 140 /** 141 * Removes this listener from device discovery notifications. The given callback is guaranteed 142 * not to receive any invocations that happen after this method is invoked. 143 * 144 * @param callback the callback for the previously started discovery to be ended 145 * @hide 146 */ 147 // Suppressed lint: Registration methods should have overload that accepts delivery Executor. 148 // Already have executor in startDiscovery() method. 149 @SuppressLint("ExecutorRegistration") 150 // TODO(b/290092977): Add back after M-2023-11 release - @SystemApi(client = MODULE_LIBRARIES) 151 // TODO(b/297301535): @RequiresPermission(MANAGE_REMOTE_AUTH) stopDiscovery(@onNull DeviceDiscoveryCallback callback)152 public void stopDiscovery(@NonNull DeviceDiscoveryCallback callback) { 153 Preconditions.checkNotNull(callback, "invalid null scanCallback"); 154 try { 155 DeviceDiscoveryListenerTransport transport; 156 synchronized (mDiscoveryListeners) { 157 WeakReference<DeviceDiscoveryListenerTransport> reference = 158 mDiscoveryListeners.remove(callback); 159 transport = (reference != null) ? reference.get() : null; 160 } 161 if (transport != null) { 162 mService.unregisterDiscoveryListener( 163 transport, 164 transport.getUserId(), 165 mContext.getPackageName(), 166 mContext.getAttributionTag()); 167 } else { 168 Log.d( 169 TAG, 170 "Cannot stop discovery with this callback " 171 + "because it is not registered."); 172 } 173 } catch (RemoteException e) { 174 throw e.rethrowFromSystemServer(); 175 } 176 } 177 178 private class DeviceDiscoveryListenerTransport extends IDeviceDiscoveryListener.Stub { 179 180 private volatile @NonNull DeviceDiscoveryCallback mDeviceDiscoveryCallback; 181 private Executor mExecutor; 182 private @UserIdInt int mUserId; 183 DeviceDiscoveryListenerTransport( DeviceDiscoveryCallback deviceDiscoveryCallback, @UserIdInt int userId, @CallbackExecutor Executor executor)184 DeviceDiscoveryListenerTransport( 185 DeviceDiscoveryCallback deviceDiscoveryCallback, 186 @UserIdInt int userId, 187 @CallbackExecutor Executor executor) { 188 Preconditions.checkNotNull(deviceDiscoveryCallback, "invalid null callback"); 189 mDeviceDiscoveryCallback = deviceDiscoveryCallback; 190 mUserId = userId; 191 mExecutor = executor; 192 } 193 194 @UserIdInt getUserId()195 int getUserId() { 196 return mUserId; 197 } 198 199 @Override onDiscovered(RemoteDevice remoteDevice)200 public void onDiscovered(RemoteDevice remoteDevice) throws RemoteException { 201 if (remoteDevice == null) { 202 Log.w(TAG, "onDiscovered is called with null device"); 203 return; 204 } 205 Log.i(TAG, "Notifying the caller about discovered: " + remoteDevice); 206 mExecutor.execute( 207 () -> { 208 mDeviceDiscoveryCallback.onDeviceUpdate(remoteDevice, STATE_SEEN); 209 }); 210 } 211 212 @Override onLost(RemoteDevice remoteDevice)213 public void onLost(RemoteDevice remoteDevice) throws RemoteException { 214 if (remoteDevice == null) { 215 Log.w(TAG, "onLost is called with null device"); 216 return; 217 } 218 Log.i(TAG, "Notifying the caller about lost: " + remoteDevice); 219 mExecutor.execute( 220 () -> { 221 mDeviceDiscoveryCallback.onDeviceUpdate(remoteDevice, STATE_LOST); 222 }); 223 } 224 225 @Override onTimeout()226 public void onTimeout() { 227 Log.i(TAG, "Notifying the caller about discovery timeout"); 228 mExecutor.execute( 229 () -> { 230 mDeviceDiscoveryCallback.onTimeout(); 231 }); 232 synchronized (mDiscoveryListeners) { 233 mDiscoveryListeners.remove(mDeviceDiscoveryCallback); 234 } 235 mDeviceDiscoveryCallback = null; 236 } 237 } 238 } 239