1 /*
2  * Copyright (C) 2019 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 com.android.car.companiondevicesupport.feature.trust;
18 
19 import static com.android.car.connecteddevice.util.SafeLog.logd;
20 import static com.android.car.connecteddevice.util.SafeLog.loge;
21 import static com.android.car.connecteddevice.util.SafeLog.logw;
22 
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.app.ActivityManager;
26 import android.app.Notification;
27 import android.app.NotificationChannel;
28 import android.app.NotificationManager;
29 import android.app.PendingIntent;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.os.IBinder;
33 import android.os.RemoteException;
34 import android.os.UserHandle;
35 
36 import androidx.core.content.ContextCompat;
37 import androidx.room.Room;
38 
39 import com.android.car.companiondevicesupport.R;
40 import com.android.car.companiondevicesupport.api.external.AssociatedDevice;
41 import com.android.car.companiondevicesupport.api.external.CompanionDevice;
42 import com.android.car.companiondevicesupport.api.external.IConnectedDeviceManager;
43 import com.android.car.companiondevicesupport.api.external.IDeviceAssociationCallback;
44 import com.android.car.companiondevicesupport.api.internal.trust.ITrustedDeviceEnrollmentCallback;
45 import com.android.car.companiondevicesupport.api.internal.trust.ITrustedDeviceAgentDelegate;
46 import com.android.car.companiondevicesupport.api.internal.trust.ITrustedDeviceCallback;
47 import com.android.car.companiondevicesupport.api.internal.trust.ITrustedDeviceManager;
48 import com.android.car.companiondevicesupport.api.internal.trust.TrustedDevice;
49 import com.android.car.companiondevicesupport.feature.trust.storage.TrustedDeviceDao;
50 import com.android.car.companiondevicesupport.feature.trust.storage.TrustedDeviceDatabase;
51 import com.android.car.companiondevicesupport.feature.trust.storage.TrustedDeviceEntity;
52 import com.android.car.companiondevicesupport.feature.trust.ui.TrustedDeviceActivity;
53 import com.android.car.companiondevicesupport.protos.PhoneAuthProto.PhoneCredentials;
54 import com.android.car.companiondevicesupport.protos.TrustedDeviceMessageProto.TrustedDeviceError;
55 import com.android.car.companiondevicesupport.protos.TrustedDeviceMessageProto.TrustedDeviceError.ErrorType;
56 import com.android.car.companiondevicesupport.protos.TrustedDeviceMessageProto.TrustedDeviceMessage.MessageType;
57 import com.android.car.companiondevicesupport.protos.TrustedDeviceMessageProto.TrustedDeviceMessage;
58 import com.android.car.connecteddevice.util.ByteUtils;
59 import com.android.car.connecteddevice.util.RemoteCallbackBinder;
60 import com.android.car.protobuf.ByteString;
61 import com.android.car.protobuf.InvalidProtocolBufferException;
62 
63 import java.util.ArrayList;
64 import java.util.List;
65 import java.util.Map;
66 import java.util.Set;
67 import java.util.concurrent.ConcurrentHashMap;
68 import java.util.concurrent.CopyOnWriteArraySet;
69 import java.util.concurrent.Executor;
70 import java.util.concurrent.Executors;
71 import java.util.concurrent.atomic.AtomicBoolean;
72 import java.util.function.Consumer;
73 
74 
75 /** Manager for the feature of unlocking the head unit with a user's trusted device. */
76 public class TrustedDeviceManager extends ITrustedDeviceManager.Stub {
77     private static final String TAG = "TrustedDeviceManager";
78 
79     private static final String CHANNEL_ID = "trusteddevice_notification_channel";
80 
81     private static final int ENROLLMENT_NOTIFICATION_ID = 0;
82 
83     private static final int TRUSTED_DEVICE_MESSAGE_VERSION = 2;
84 
85     /** Length of token generated on a trusted device. */
86     private static final int ESCROW_TOKEN_LENGTH = 8;
87 
88     private final Map<IBinder, ITrustedDeviceCallback> mTrustedDeviceCallbacks =
89             new ConcurrentHashMap<>();
90 
91     private final Map<IBinder, ITrustedDeviceEnrollmentCallback> mEnrollmentCallbacks
92             = new ConcurrentHashMap<>();
93 
94     private final Map<IBinder, IDeviceAssociationCallback> mAssociatedDeviceCallbacks =
95             new ConcurrentHashMap<>();
96 
97     private final Set<RemoteCallbackBinder> mCallbackBinders = new CopyOnWriteArraySet<>();
98 
99     private final Context mContext;
100 
101     private final TrustedDeviceFeature mTrustedDeviceFeature;
102 
103     private final Executor mExecutor = Executors.newSingleThreadExecutor();
104 
105     private final AtomicBoolean mIsWaitingForCredentials = new AtomicBoolean(false);
106 
107     private final NotificationManager mNotificationManager;
108 
109     private TrustedDeviceDao mDatabase;
110 
111     private ITrustedDeviceAgentDelegate mTrustAgentDelegate;
112 
113     private CompanionDevice mPendingDevice;
114 
115     private byte[] mPendingToken;
116 
117     private PendingCredentials mPendingCredentials;
118 
TrustedDeviceManager(@onNull Context context)119     TrustedDeviceManager(@NonNull Context context) {
120         mContext = context;
121         mTrustedDeviceFeature = new TrustedDeviceFeature(context);
122         mTrustedDeviceFeature.setCallback(mFeatureCallback);
123         mTrustedDeviceFeature.setAssociatedDeviceCallback(mAssociatedDeviceCallback);
124         mTrustedDeviceFeature.start();
125         mDatabase = Room.databaseBuilder(context, TrustedDeviceDatabase.class,
126                 TrustedDeviceDatabase.DATABASE_NAME).build().trustedDeviceDao();
127         mNotificationManager = (NotificationManager) mContext.
128                 getSystemService(Context.NOTIFICATION_SERVICE);
129         String channelName = mContext.getString(R.string.trusted_device_notification_channel_name);
130         NotificationChannel channel = new NotificationChannel(CHANNEL_ID, channelName,
131                 NotificationManager.IMPORTANCE_HIGH);
132         mNotificationManager.createNotificationChannel(channel);
133         logd(TAG, "TrustedDeviceManager created successfully.");
134     }
135 
cleanup()136     void cleanup() {
137         mPendingToken = null;
138         mPendingDevice = null;
139         mPendingCredentials = null;
140         mIsWaitingForCredentials.set(false);
141         mTrustedDeviceCallbacks.clear();
142         mEnrollmentCallbacks.clear();
143         mAssociatedDeviceCallbacks.clear();
144         mTrustedDeviceFeature.stop();
145     }
146 
startEnrollment(@onNull CompanionDevice device, @NonNull byte[] token)147     private void startEnrollment(@NonNull CompanionDevice device, @NonNull byte[] token) {
148         logd(TAG, "Starting trusted device enrollment process.");
149         mPendingDevice = device;
150         showEnrollmentNotification();
151 
152         mPendingToken = token;
153         if (mTrustAgentDelegate == null) {
154             logd(TAG, "No trust agent delegate has been set yet. No further enrollment action "
155                     + "can be taken at this time.");
156             return;
157         }
158 
159         try {
160             mTrustAgentDelegate.addEscrowToken(token, ActivityManager.getCurrentUser());
161         } catch (RemoteException e) {
162             loge(TAG, "Error while adding token through delegate.", e);
163         }
164     }
165 
showEnrollmentNotification()166     private void showEnrollmentNotification() {
167         UserHandle currentUser = UserHandle.of(ActivityManager.getCurrentUser());
168         Intent enrollmentIntent = new Intent(mContext, TrustedDeviceActivity.class)
169                 // Setting this action ensures that the TrustedDeviceActivity is resumed if it is
170                 // already running.
171                 .setAction("com.android.settings.action.EXTRA_SETTINGS")
172                 .putExtra(TrustedDeviceConstants.INTENT_EXTRA_ENROLL_NEW_TOKEN, true)
173                 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
174         PendingIntent pendingIntent = PendingIntent
175                 .getActivityAsUser(mContext, /* requestCode = */ 0, enrollmentIntent,
176                         PendingIntent.FLAG_UPDATE_CURRENT, null, currentUser);
177         Notification notification = new Notification.Builder(mContext, CHANNEL_ID)
178                 .setSmallIcon(R.drawable.ic_directions_car_filled)
179                 .setColor(ContextCompat.getColor(mContext, R.color.car_red_300))
180                 .setContentTitle(mContext.getString(R.string.trusted_device_notification_title))
181                 .setContentText(mContext.getString(R.string.trusted_device_notification_content))
182                 .setContentIntent(pendingIntent)
183                 .setAutoCancel(true)
184                 .build();
185         mNotificationManager.notifyAsUser(/* tag = */ null, ENROLLMENT_NOTIFICATION_ID,
186                 notification, currentUser);
187     }
188 
unlockUser(@onNull String deviceId, @NonNull PhoneCredentials credentials)189     private void unlockUser(@NonNull String deviceId, @NonNull PhoneCredentials credentials) {
190         logd(TAG, "Unlocking with credentials.");
191         try {
192             mTrustAgentDelegate.unlockUserWithToken(credentials.getEscrowToken().toByteArray(),
193                     ByteUtils.bytesToLong(credentials.getHandle().toByteArray()),
194                     ActivityManager.getCurrentUser());
195             mTrustedDeviceFeature.sendMessageSecurely(deviceId, createAcknowledgmentMessage());
196         } catch (RemoteException e) {
197             loge(TAG, "Error while unlocking user through delegate.", e);
198         }
199     }
200 
201     @Override
onEscrowTokenAdded(int userId, long handle)202     public void onEscrowTokenAdded(int userId, long handle) {
203         logd(TAG, "Escrow token has been successfully added.");
204         mPendingToken = null;
205 
206         if (mEnrollmentCallbacks.size() == 0) {
207             mIsWaitingForCredentials.set(true);
208             return;
209         }
210 
211         mIsWaitingForCredentials.set(false);
212         notifyEnrollmentCallbacks(callback -> {
213             try {
214                 callback.onValidateCredentialsRequest();
215             } catch (RemoteException e) {
216                 loge(TAG, "Error while requesting credential validation.", e);
217             }
218         });
219     }
220 
221     @Override
onEscrowTokenActivated(int userId, long handle)222     public void onEscrowTokenActivated(int userId, long handle) {
223         if (mPendingDevice == null) {
224             loge(TAG, "Unable to complete device enrollment. Pending device was null.");
225             return;
226         }
227 
228         logd(TAG, "Enrollment completed successfully! Sending handle to connected device and "
229                 + "persisting trusted device record.");
230 
231         mTrustedDeviceFeature.sendMessageSecurely(mPendingDevice, createHandleMessage(handle));
232 
233         String deviceId = mPendingDevice.getDeviceId();
234 
235         TrustedDeviceEntity entity = new TrustedDeviceEntity();
236         entity.id = deviceId;
237         entity.userId = userId;
238         entity.handle = handle;
239         mDatabase.addOrReplaceTrustedDevice(entity);
240 
241         mPendingDevice = null;
242 
243         TrustedDevice trustedDevice = new TrustedDevice(deviceId, userId, handle);
244         notifyTrustedDeviceCallbacks(callback -> {
245             try {
246                 callback.onTrustedDeviceAdded(trustedDevice);
247             } catch (RemoteException e) {
248                 loge(TAG, "Failed to notify that enrollment completed successfully.", e);
249             }
250         });
251     }
252 
253     @Override
getTrustedDevicesForActiveUser()254     public List<TrustedDevice> getTrustedDevicesForActiveUser() {
255         List<TrustedDeviceEntity> foundEntities =
256                 mDatabase.getTrustedDevicesForUser(ActivityManager.getCurrentUser());
257 
258         List<TrustedDevice> trustedDevices = new ArrayList<>();
259         if (foundEntities == null) {
260             return trustedDevices;
261         }
262 
263         for (TrustedDeviceEntity entity : foundEntities) {
264             trustedDevices.add(entity.toTrustedDevice());
265         }
266 
267         return trustedDevices;
268     }
269 
270     @Override
removeTrustedDevice(TrustedDevice trustedDevice)271     public void removeTrustedDevice(TrustedDevice trustedDevice) {
272         if (mTrustAgentDelegate == null) {
273             loge(TAG, "No TrustAgent delegate has been set. Unable to remove trusted device.");
274             return;
275         }
276 
277         try {
278             mTrustAgentDelegate.removeEscrowToken(trustedDevice.getHandle(),
279                     trustedDevice.getUserId());
280             mDatabase.removeTrustedDevice(new TrustedDeviceEntity(trustedDevice));
281         } catch (RemoteException e) {
282             loge(TAG, "Error while removing token through delegate.", e);
283             return;
284         }
285         notifyTrustedDeviceCallbacks(callback -> {
286             try {
287                 callback.onTrustedDeviceRemoved(trustedDevice);
288             } catch (RemoteException e) {
289                 loge(TAG, "Failed to notify that a trusted device has been removed.", e);
290             }
291         });
292     }
293 
294     @Override
getActiveUserConnectedDevices()295     public List<CompanionDevice> getActiveUserConnectedDevices() {
296         List<CompanionDevice> devices = new ArrayList<>();
297         IConnectedDeviceManager manager = mTrustedDeviceFeature.getConnectedDeviceManager();
298         if (manager == null) {
299             loge(TAG, "Unable to get connected devices. Service not connected. ");
300             return devices;
301         }
302         try {
303             devices = manager.getActiveUserConnectedDevices();
304         } catch (RemoteException e) {
305             loge(TAG, "Failed to get connected devices. ", e);
306         }
307         return devices;
308     }
309 
310     @Override
initiateEnrollment(String deviceId)311     public void initiateEnrollment(String deviceId) {
312         mTrustedDeviceFeature.sendMessageSecurely(deviceId, createStartEnrollmentMessage());
313     }
314 
315     @Override
registerTrustedDeviceCallback(ITrustedDeviceCallback callback)316     public void registerTrustedDeviceCallback(ITrustedDeviceCallback callback)  {
317         mTrustedDeviceCallbacks.put(callback.asBinder(), callback);
318         RemoteCallbackBinder remoteBinder = new RemoteCallbackBinder(callback.asBinder(), iBinder ->
319                 unregisterTrustedDeviceCallback(callback));
320         mCallbackBinders.add(remoteBinder);
321     }
322 
323     @Override
unregisterTrustedDeviceCallback(ITrustedDeviceCallback callback)324     public void unregisterTrustedDeviceCallback(ITrustedDeviceCallback callback) {
325         IBinder binder = callback.asBinder();
326         mTrustedDeviceCallbacks.remove(binder);
327         removeRemoteBinder(binder);
328     }
329 
330     @Override
registerAssociatedDeviceCallback(IDeviceAssociationCallback callback)331     public void registerAssociatedDeviceCallback(IDeviceAssociationCallback callback) {
332         mAssociatedDeviceCallbacks.put(callback.asBinder(), callback);
333         RemoteCallbackBinder remoteBinder = new RemoteCallbackBinder(callback.asBinder(), iBinder ->
334                 unregisterAssociatedDeviceCallback(callback));
335         mCallbackBinders.add(remoteBinder);
336     }
337 
338     @Override
unregisterAssociatedDeviceCallback(IDeviceAssociationCallback callback)339     public void unregisterAssociatedDeviceCallback(IDeviceAssociationCallback callback) {
340         IBinder binder = callback.asBinder();
341         mAssociatedDeviceCallbacks.remove(binder);
342         removeRemoteBinder(binder);
343     }
344 
345     @Override
registerTrustedDeviceEnrollmentCallback( ITrustedDeviceEnrollmentCallback callback)346     public void registerTrustedDeviceEnrollmentCallback(
347             ITrustedDeviceEnrollmentCallback callback) {
348         mEnrollmentCallbacks.put(callback.asBinder(), callback);
349         RemoteCallbackBinder remoteBinder = new RemoteCallbackBinder(callback.asBinder(),
350                 iBinder -> unregisterTrustedDeviceEnrollmentCallback(callback));
351         mCallbackBinders.add(remoteBinder);
352         // A token has been added and is waiting on user credential validation.
353         if (mIsWaitingForCredentials.getAndSet(false)) {
354             mExecutor.execute(() -> {
355                 try {
356                     callback.onValidateCredentialsRequest();
357                 } catch (RemoteException e) {
358                     loge(TAG, "Error while notifying enrollment listener.", e);
359                 }
360             });
361         }
362     }
363 
364     @Override
unregisterTrustedDeviceEnrollmentCallback( ITrustedDeviceEnrollmentCallback callback)365     public void unregisterTrustedDeviceEnrollmentCallback(
366             ITrustedDeviceEnrollmentCallback callback) {
367         IBinder binder = callback.asBinder();
368         mEnrollmentCallbacks.remove(binder);
369         removeRemoteBinder(binder);
370     }
371 
372     @Override
setTrustedDeviceAgentDelegate(ITrustedDeviceAgentDelegate trustAgentDelegate)373     public void setTrustedDeviceAgentDelegate(ITrustedDeviceAgentDelegate trustAgentDelegate) {
374         logd(TAG, "Set trusted device agent delegate: " + trustAgentDelegate + ".");
375         mTrustAgentDelegate = trustAgentDelegate;
376 
377         // Add pending token if present.
378         if (mPendingToken != null) {
379             try {
380                 trustAgentDelegate.addEscrowToken(mPendingToken, ActivityManager.getCurrentUser());
381             } catch (RemoteException e) {
382                 loge(TAG, "Error while adding token through delegate.", e);
383             }
384             return;
385         }
386 
387         // Unlock with pending credentials if present.
388         if (mPendingCredentials != null) {
389             unlockUser(mPendingCredentials.mDeviceId, mPendingCredentials.mPhoneCredentials);
390             mPendingCredentials = null;
391         }
392     }
393 
394     @Override
clearTrustedDeviceAgentDelegate(ITrustedDeviceAgentDelegate trustAgentDelegate)395     public void clearTrustedDeviceAgentDelegate(ITrustedDeviceAgentDelegate trustAgentDelegate) {
396         if (trustAgentDelegate.asBinder() != mTrustAgentDelegate.asBinder()) {
397             logd(TAG, "TrustedDeviceAgentDelegate " + trustAgentDelegate + " doesn't match the " +
398                     "current TrustedDeviceAgentDelegate: " + mTrustAgentDelegate +
399                     ". Ignoring call to clear.");
400             return;
401         }
402         logd(TAG, "Clear current TrustedDeviceAgentDelegate: " + trustAgentDelegate + ".");
403         mTrustAgentDelegate = null;
404     }
405 
areCredentialsValid(@ullable PhoneCredentials credentials)406     private boolean areCredentialsValid(@Nullable PhoneCredentials credentials) {
407         return credentials != null && credentials.getEscrowToken() != null
408                 && credentials.getHandle() != null;
409     }
410 
processEnrollmentMessage(@onNull CompanionDevice device, @Nullable ByteString payload)411     private void processEnrollmentMessage(@NonNull CompanionDevice device,
412             @Nullable ByteString payload) {
413         if (payload == null) {
414             logw(TAG, "Received enrollment message with null payload. Ignoring.");
415             return;
416         }
417 
418         byte[] message = payload.toByteArray();
419         if (message.length != ESCROW_TOKEN_LENGTH) {
420             logw(TAG, "Received invalid escrow token of length " + message.length + ". Ignoring.");
421             return;
422         }
423 
424         startEnrollment(device, message);
425     }
426 
processUnlockMessage(@onNull CompanionDevice device, @Nullable ByteString payload)427     private void processUnlockMessage(@NonNull CompanionDevice device,
428             @Nullable ByteString payload) {
429         if (payload == null) {
430             logw(TAG, "Received unlock message with null payload. Ignoring.");
431             return;
432         }
433         byte[] message = payload.toByteArray();
434 
435         PhoneCredentials credentials = null;
436         try {
437             credentials = PhoneCredentials.parseFrom(message);
438         } catch (InvalidProtocolBufferException e) {
439             loge(TAG, "Unable to parse credentials from device. Not unlocking head unit.");
440             return;
441         }
442 
443         if (!areCredentialsValid(credentials)) {
444             loge(TAG, "Received invalid credentials from device. Not unlocking head unit.");
445             return;
446         }
447 
448         TrustedDeviceEntity entity = mDatabase.getTrustedDevice(device.getDeviceId());
449 
450         if (entity == null) {
451             logw(TAG, "Received unlock request from an untrusted device.");
452             // TODO(b/145618412) Notify device that it is no longer trusted.
453             return;
454         }
455 
456         if (entity.userId != ActivityManager.getCurrentUser()) {
457             logw(TAG, "Received credentials from background user " + entity.userId
458                     + ". Ignoring.");
459             return;
460         }
461 
462         TrustedDeviceEventLog.onCredentialsReceived();
463 
464         if (mTrustAgentDelegate == null) {
465             logd(TAG, "No trust agent delegate set yet. Credentials will be delivered once "
466                     + "set.");
467             mPendingCredentials = new PendingCredentials(device.getDeviceId(), credentials);
468             return;
469         }
470 
471         logd(TAG, "Received unlock credentials from trusted device " + device.getDeviceId()
472                 + ". Attempting unlock.");
473 
474         unlockUser(device.getDeviceId(), credentials);
475     }
476 
processErrorMessage(@ullable ByteString payload)477     private void processErrorMessage(@Nullable ByteString payload) {
478         if (payload == null) {
479             logw(TAG, "Received error message with null payload. Ignoring.");
480             return;
481         }
482         TrustedDeviceError trustedDeviceError = null;
483         try {
484             trustedDeviceError = TrustedDeviceError.parseFrom(payload.toByteArray());
485         } catch (InvalidProtocolBufferException e) {
486             loge(TAG, "Received error message from client, but cannot parse.", e);
487             notifyEnrollmentError(TrustedDeviceConstants.TRUSTED_DEVICE_ERROR_UNKNOWN);
488             return;
489         }
490         int errorType = trustedDeviceError.getTypeValue();
491         int error;
492         switch (errorType) {
493             case ErrorType.MESSAGE_TYPE_UNKNOWN_VALUE:
494                 error = TrustedDeviceConstants.TRUSTED_DEVICE_ERROR_MESSAGE_TYPE_UNKNOWN;
495                 break;
496             case ErrorType.DEVICE_NOT_SECURED_VALUE:
497                 error = TrustedDeviceConstants.TRUSTED_DEVICE_ERROR_DEVICE_NOT_SECURED;
498                 break;
499             default:
500                 loge(TAG, "Encountered unexpected error type: " + errorType + ".");
501                 error = TrustedDeviceConstants.TRUSTED_DEVICE_ERROR_UNKNOWN;
502         }
503         notifyEnrollmentError(error);
504     }
505 
notifyEnrollmentError(@rustedDeviceConstants.TrustedDeviceError int error)506     private void notifyEnrollmentError(@TrustedDeviceConstants.TrustedDeviceError int error) {
507         notifyEnrollmentCallbacks(callback -> {
508             try {
509                 callback.onTrustedDeviceEnrollmentError(error);
510             } catch (RemoteException e) {
511                 loge(TAG, "Error while notifying enrollment error.", e);
512             }
513         });
514     }
515 
notifyTrustedDeviceCallbacks(Consumer<ITrustedDeviceCallback> notification)516     private void notifyTrustedDeviceCallbacks(Consumer<ITrustedDeviceCallback> notification) {
517         mTrustedDeviceCallbacks.forEach((iBinder, callback) -> mExecutor.execute(() ->
518                 notification.accept(callback)));
519     }
520 
notifyEnrollmentCallbacks( Consumer<ITrustedDeviceEnrollmentCallback> notification )521     private void notifyEnrollmentCallbacks(
522             Consumer<ITrustedDeviceEnrollmentCallback> notification ) {
523         mEnrollmentCallbacks.forEach((iBinder, callback) -> mExecutor.execute(() ->
524                 notification.accept(callback)));
525     }
526 
notifyAssociatedDeviceCallbacks( Consumer<IDeviceAssociationCallback> notification )527     private void notifyAssociatedDeviceCallbacks(
528             Consumer<IDeviceAssociationCallback> notification ) {
529         mAssociatedDeviceCallbacks.forEach((iBinder, callback) -> mExecutor.execute(() ->
530                 notification.accept(callback)));
531     }
532 
removeRemoteBinder(IBinder binder)533     private void removeRemoteBinder(IBinder binder) {
534         RemoteCallbackBinder remoteBinderToRemove = null;
535         for (RemoteCallbackBinder remoteBinder : mCallbackBinders) {
536             if (remoteBinder.getCallbackBinder().equals(binder)) {
537                 remoteBinderToRemove = remoteBinder;
538                 break;
539             }
540         }
541         if (remoteBinderToRemove != null) {
542             remoteBinderToRemove.cleanUp();
543             mCallbackBinders.remove(remoteBinderToRemove);
544         }
545     }
546 
createAcknowledgmentMessage()547     private byte[] createAcknowledgmentMessage() {
548         return TrustedDeviceMessage.newBuilder()
549                 .setVersion(TRUSTED_DEVICE_MESSAGE_VERSION)
550                 .setType(MessageType.ACK)
551                 .build()
552                 .toByteArray();
553     }
554 
createHandleMessage(long handle)555     private byte[] createHandleMessage(long handle) {
556         return TrustedDeviceMessage.newBuilder()
557                 .setVersion(TRUSTED_DEVICE_MESSAGE_VERSION)
558                 .setType(MessageType.HANDLE)
559                 .setPayload(ByteString.copyFrom(ByteUtils.longToBytes(handle)))
560                 .build()
561                 .toByteArray();
562     }
563 
createStartEnrollmentMessage()564     private byte[] createStartEnrollmentMessage() {
565         return TrustedDeviceMessage.newBuilder()
566                 .setVersion(TRUSTED_DEVICE_MESSAGE_VERSION)
567                 .setType(MessageType.START_ENROLLMENT)
568                 .build()
569                 .toByteArray();
570     }
571 
572     private final TrustedDeviceFeature.Callback mFeatureCallback =
573             new TrustedDeviceFeature.Callback() {
574         @Override
575         public void onMessageReceived(CompanionDevice device, byte[] message) {
576             TrustedDeviceMessage trustedDeviceMessage = null;
577             try {
578                 trustedDeviceMessage = TrustedDeviceMessage.parseFrom(message);
579             } catch (InvalidProtocolBufferException e) {
580                 loge(TAG, "Received message from client, but cannot parse.", e);
581                 return;
582             }
583 
584             switch (trustedDeviceMessage.getType()) {
585                 case ESCROW_TOKEN:
586                     processEnrollmentMessage(device, trustedDeviceMessage.getPayload());
587                     break;
588                 case UNLOCK_CREDENTIALS:
589                     processUnlockMessage(device, trustedDeviceMessage.getPayload());
590                     break;
591                 case ACK:
592                     // The client sends an acknowledgment when the handle has been received, but
593                     // nothing needs to be on the IHU side. So simply log this message.
594                     logd(TAG, "Received acknowledgment message from client.");
595                     break;
596                 case ERROR:
597                     processErrorMessage(trustedDeviceMessage.getPayload());
598                     break;
599                 default:
600                     // The client should only be sending requests to either enroll or unlock.
601                     loge(TAG, "Received a message from the client with an invalid MessageType ( "
602                             + trustedDeviceMessage.getType() + "). Ignoring.");
603             }
604         }
605 
606         @Override
607         public void onDeviceError(CompanionDevice device, int error) {
608         }
609     };
610 
611     private final TrustedDeviceFeature.AssociatedDeviceCallback mAssociatedDeviceCallback =
612             new TrustedDeviceFeature.AssociatedDeviceCallback() {
613         @Override
614         public void onAssociatedDeviceAdded(AssociatedDevice device) {
615             notifyAssociatedDeviceCallbacks(callback -> {
616                 try {
617                     callback.onAssociatedDeviceAdded(device);
618                 } catch (RemoteException e) {
619                     loge(TAG, "Failed to notify that an associated device has been added.", e);
620                 }
621             });
622         }
623 
624         @Override
625         public void onAssociatedDeviceRemoved(AssociatedDevice device) {
626             List<TrustedDevice> devices = getTrustedDevicesForActiveUser();
627             if (devices == null || devices.isEmpty()) {
628                 return;
629             }
630             TrustedDevice deviceToRemove = null;
631             for (TrustedDevice trustedDevice : devices) {
632                 if (trustedDevice.getDeviceId().equals(device.getDeviceId())) {
633                     deviceToRemove = trustedDevice;
634                     break;
635                 }
636             }
637             if (deviceToRemove != null) {
638                 removeTrustedDevice(deviceToRemove);
639             }
640             notifyAssociatedDeviceCallbacks(callback -> {
641                 try {
642                     callback.onAssociatedDeviceRemoved(device);
643                 } catch (RemoteException e) {
644                     loge(TAG, "Failed to notify that an associated device has been " +
645                             "removed.", e);
646                 }
647             });
648         }
649 
650         @Override
651         public void onAssociatedDeviceUpdated(AssociatedDevice device) {
652             notifyAssociatedDeviceCallbacks(callback -> {
653                 try {
654                     callback.onAssociatedDeviceUpdated(device);
655                 } catch (RemoteException e) {
656                     loge(TAG, "Failed to notify that an associated device has been " +
657                             "updated.", e);
658                 }
659             });
660         }
661     };
662 
663     private static class PendingCredentials {
664         final String mDeviceId;
665         final PhoneCredentials mPhoneCredentials;
666 
PendingCredentials(@onNull String deviceId, @NonNull PhoneCredentials credentials)667         PendingCredentials(@NonNull String deviceId, @NonNull PhoneCredentials credentials) {
668             mDeviceId = deviceId;
669             mPhoneCredentials = credentials;
670         }
671     }
672 }
673