Enroll a trusted device with a whole workflow

Also fixed the bug in robolectric test

Bug: 122265774

Test: Manual, Robotest

Change-Id: I8acb6268fb8274e6b1fe9b1c75add688b93f4d9c
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index d57f273..38080f8 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -33,6 +33,7 @@
     <uses-permission android:name="android.permission.BLUETOOTH"/>
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
     <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED"/>
+    <uses-permission android:name="android.permission.CAR_ENROLL_TRUST"/>
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
     <uses-permission android:name="android.permission.CLEAR_APP_USER_DATA"/>
     <uses-permission android:name="android.permission.DELETE_CACHE_FILES"/>
diff --git a/res/values/preference_keys.xml b/res/values/preference_keys.xml
index 901b4f7..539f640 100644
--- a/res/values/preference_keys.xml
+++ b/res/values/preference_keys.xml
@@ -173,8 +173,12 @@
     <string name="pk_pin_lock" translatable="false">pin_lock</string>
     <string name="pk_choose_lock_type" translatable="false">choose_lock_type</string>
     <string name="pk_trusted_device" translatable="false">trusted_device</string>
+    <string name="pk_car_ble_name" translatable="false">car_ble_name</string>
     <string name="pk_add_trusted_device" translatable="false">add_trusted_device</string>
     <string name="pk_trusted_device_list" translatable="false">trusted_device_list</string>
+    <string name="pk_add_device_instruction" translatable="false">add_device_instruction</string>
+    <string name="pk_companion_app_download" translatable="false">companion_app_download</string>
+    <string name="pk_trusted_device_safety_alert" translatable="false">trusted_device_safety_alert</string>
 
     <!-- System Settings -->
     <string name="pk_languages_and_input_settings" translatable="false">
diff --git a/res/values/strings.xml b/res/values/strings.xml
index ea2a70e..553a4de 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -851,6 +851,22 @@
     <string name="trusted_device">Trusted devices</string>
     <!-- Subtitle of trusted device button in security page [CHAR LIMIT=30]-->
     <string name="trusted_device_subtitle">Device security</string>
+    <!-- Confirm button label of confirm pairing code dialog [CHAR LIMIT=30]-->
+    <string name="trusted_device_confirm_button">Confirm</string>
+    <!-- Message shown on the add trusted device page [CHAR LIMIT=NONE]-->
+    <string name="add_trusted_device_instruction">Use the companion app to set up a trusted device. Once set up, you will be able to unlock your user profile when your phone is detected by the vehicle</string>
+    <!-- Step one of adding a trusted device [CHAR LIMIT=NONE]-->
+    <string name="trusted_device_download_app">1. Download the companion app on your phone</string>
+    <!-- Step two of adding a trusted device [CHAR LIMIT=NONE]-->
+    <string name="trusted_device_select_device">2. Select <xliff:g id="car_name" example="MyCar">%1$s</xliff:g> on your phone to pair the devices</string>
+    <!-- Messages shown to alert user about the safety of the trusted device feature [CHAR LIMIT=NONE]-->
+    <string name="trusted_device_safety_alert">Smart Lock can\'t detect security features of this device. To help protect your car, trusted device will only be able to keep your car unlocked once it\'s already unlocked by you. Your trusted device can keep your car unlocked when it\'s nearby, even if someone else is holding it.</string>
+    <!-- Confirm pairing code dialog title [CHAR LIMIT=60]-->
+    <string name="trusted_device_pairing_code_dialog_title">Add <xliff:g id="device_name" example="Pixel2">%1$s</xliff:g> as a trusted device</string>
+    <!-- Toast shown when user enrolls a device successfully [CHAR LIMIT=100]-->
+    <string name="trusted_device_success_enrollment_toast"><xliff:g id="device_name" example="Pixel2">%1$s</xliff:g> successfully added as a trusted device</string>
+    <!-- Toast shown when enrolling a device fails [CHAR LIMIT=50]-->
+    <string name="trusted_device_fail_enrollment_toast"><xliff:g id="device_name" example="Pixel2">%1$s</xliff:g> enrollment failed</string>
 
     <!-- generic --><skip/>
     <!-- Button label for generic forget action [CHAR LIMIT=20] -->
diff --git a/res/xml/add_trusted_device_progress_fragment.xml b/res/xml/add_trusted_device_progress_fragment.xml
new file mode 100644
index 0000000..a7921f3
--- /dev/null
+++ b/res/xml/add_trusted_device_progress_fragment.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+                  xmlns:settings="http://schemas.android.com/apk/res-auto"
+                  android:title="@string/add_device_title">
+    <Preference
+        android:key="@string/pk_add_device_instruction"
+        android:summary="@string/add_trusted_device_instruction"/>
+    <Preference
+        android:key="@string/pk_companion_app_download"
+        android:title="@string/trusted_device_download_app"/>
+    <Preference
+        android:key="@string/pk_car_ble_name"
+        settings:controller="com.android.car.settings.security.ChooseDeviceInstructionPreferenceController"/>
+    <Preference
+        android:key="@string/pk_trusted_device_safety_alert"
+        android:summary="@string/trusted_device_safety_alert"/>
+</PreferenceScreen>
diff --git a/res/xml/choose_trusted_device_fragment.xml b/res/xml/choose_trusted_device_fragment.xml
index 9eb05d2..a8bbd4f 100644
--- a/res/xml/choose_trusted_device_fragment.xml
+++ b/res/xml/choose_trusted_device_fragment.xml
@@ -21,7 +21,9 @@
         android:key="@string/pk_trusted_device_list"
         settings:controller="com.android.car.settings.security.TrustedDeviceListPreferenceController"/>
     <Preference
+        android:fragment="com.android.car.settings.security.AddTrustedDeviceProgressFragment"
         android:icon="@drawable/ic_add"
         android:key="@string/pk_add_trusted_device"
-        android:title="@string/add_device_title"/>
+        android:title="@string/add_device_title"
+        settings:controller="com.android.car.settings.security.AddTrustedDevicePreferenceController"/>
 </PreferenceScreen>
\ No newline at end of file
diff --git a/src/com/android/car/settings/security/AddTrustedDevicePreferenceController.java b/src/com/android/car/settings/security/AddTrustedDevicePreferenceController.java
new file mode 100644
index 0000000..54c1731
--- /dev/null
+++ b/src/com/android/car/settings/security/AddTrustedDevicePreferenceController.java
@@ -0,0 +1,212 @@
+/*
+ * 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.car.settings.security;
+
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothDevice;
+import android.car.Car;
+import android.car.CarNotConnectedException;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.trust.CarTrustAgentEnrollmentManager;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.RemoteException;
+import android.preference.PreferenceManager;
+import android.view.WindowManagerGlobal;
+import android.widget.Toast;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+import com.android.settingslib.utils.ThreadUtils;
+
+/**
+ * Business logic when user click on add trusted device, a new screen will be shown and user can add
+ * a trusted device using CarTrustAgentEnrollmentManager, confirm pairing code dialog base on that.
+ * TODO(wentingzhai): test the enrollment process when CarTrustAgentEnrollment Service is done.
+ *
+ */
+public class AddTrustedDevicePreferenceController extends PreferenceController<Preference> {
+
+    private static final Logger LOG = new Logger(AddTrustedDevicePreferenceController.class);
+    private final SharedPreferences mPrefs;
+    private final Car mCar;
+    private BluetoothDevice mBluetoothDevice;
+    @Nullable
+    private CarTrustAgentEnrollmentManager mCarTrustAgentEnrollmentManager;
+
+    private final CarTrustAgentEnrollmentManager.CarTrustAgentEnrollmentCallback
+            mCarTrustAgentEnrollmentCallback =
+            new CarTrustAgentEnrollmentManager.CarTrustAgentEnrollmentCallback() {
+
+                @Override
+                public void onEnrollmentHandshakeFailure(BluetoothDevice device, int errorCode) {
+                    LOG.e("Trust agent service time out");
+                }
+
+                @Override
+                public void onAuthStringAvailable(BluetoothDevice device, String authString) {
+                    ConfirmPairingCodeDialog dialog = ConfirmPairingCodeDialog.newInstance(
+                            device.getName(), authString);
+                    dialog.setConfirmPairingCodeListener(mConfirmParingCodeListener);
+                    getFragmentController().showDialog(dialog, ConfirmPairingCodeDialog.TAG);
+                }
+
+                @Override
+                public void onEscrowTokenAdded(long handle) {
+                    try {
+                        // User need to enter the correct authentication of the car to activate the
+                        // added token.
+                        WindowManagerGlobal.getWindowManagerService().lockNow(/* options= */ null);
+                    } catch (RemoteException e) {
+                        LOG.e(e.getMessage(), e);
+                    }
+                }
+
+                @Override
+                public void onTrustRevoked(long handle, boolean success) {
+                }
+
+                @Override
+                public void onEscrowTokenActiveStateChanged(long handle, boolean active) {
+                    if (active) {
+                        ThreadUtils.postOnMainThread(
+                                () -> mPrefs.edit().putString(String.valueOf(handle),
+                                        mBluetoothDevice.getName()).apply());
+                        Toast.makeText(getContext(),
+                                getContext().getString(
+                                        R.string.trusted_device_success_enrollment_toast,
+                                        mBluetoothDevice.getName()),
+                                Toast.LENGTH_LONG).show();
+                    } else {
+                        LOG.d(handle + " has been deactivated");
+                    }
+                    try {
+                        mCarTrustAgentEnrollmentManager.stopEnrollmentAdvertising();
+                        mCarTrustAgentEnrollmentManager.setBleCallback(null);
+                        mCarTrustAgentEnrollmentManager.setEnrollmentCallback(null);
+                    } catch (CarNotConnectedException e) {
+                        LOG.e(e.getMessage(), e);
+                    }
+                    getFragmentController().goBack();
+                }
+            };
+
+    private final CarTrustAgentEnrollmentManager.CarTrustAgentBleCallback
+            mCarTrustAgentBleCallback =
+            new CarTrustAgentEnrollmentManager.CarTrustAgentBleCallback() {
+                @Override
+                public void onBleEnrollmentDeviceConnected(BluetoothDevice device) {
+                    mBluetoothDevice = device;
+                    try {
+                        mCarTrustAgentEnrollmentManager.initiateEnrollmentHandshake(
+                                mBluetoothDevice);
+                    } catch (CarNotConnectedException e) {
+                        LOG.e(e.getMessage());
+                    }
+                }
+
+                @Override
+                public void onBleEnrollmentDeviceDisconnected(BluetoothDevice device) {
+                    LOG.d("Bluetooth device " + device.getName() + "has been disconnected");
+                    mBluetoothDevice = null;
+                }
+
+                @Override
+                public void onEnrollmentAdvertisingStarted() {
+                    LOG.d("Advertising started successfully");
+                }
+
+                @Override
+                public void onEnrollmentAdvertisingFailed(int errorCode) {
+                    getFragmentController().goBack();
+                }
+            };
+
+    @VisibleForTesting
+    final ConfirmPairingCodeDialog.ConfirmPairingCodeListener mConfirmParingCodeListener =
+            new ConfirmPairingCodeDialog.ConfirmPairingCodeListener() {
+                public void onConfirmPairingCode() {
+                    try {
+                        mCarTrustAgentEnrollmentManager.enrollmentHandshakeAccepted();
+                    } catch (CarNotConnectedException e) {
+                        LOG.e(e.getMessage(), e);
+                    }
+                }
+
+                public void onDialogCancelled() {
+                    getFragmentController().goBack();
+                }
+            };
+
+    public AddTrustedDevicePreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mCar = Car.createCar(context);
+        mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
+        try {
+            mCarTrustAgentEnrollmentManager = (CarTrustAgentEnrollmentManager) mCar.getCarManager(
+                    Car.CAR_TRUST_AGENT_ENROLLMENT_SERVICE);
+        } catch (CarNotConnectedException e) {
+            LOG.e(e.getMessage(), e);
+        }
+    }
+
+    @Override
+    protected void checkInitialized() {
+        if (mCarTrustAgentEnrollmentManager == null) {
+            throw new IllegalStateException("mCarTrustAgentEnrollmentManager is null.");
+        }
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    public boolean handlePreferenceClicked(Preference preference) {
+        try {
+            mCarTrustAgentEnrollmentManager.startEnrollmentAdvertising();
+        } catch (CarNotConnectedException e) {
+            LOG.e(e.getMessage(), e);
+        }
+        // return false to make sure AddTrustedDeviceProgressFragment will show up.
+        return false;
+    }
+
+    @Override
+    protected void onStartInternal() {
+        try {
+            mCarTrustAgentEnrollmentManager.setEnrollmentCallback(
+                    mCarTrustAgentEnrollmentCallback);
+            mCarTrustAgentEnrollmentManager.setBleCallback(mCarTrustAgentBleCallback);
+        } catch (CarNotConnectedException e) {
+            LOG.e(e.getMessage(), e);
+        }
+        ConfirmPairingCodeDialog pairingCodeDialog =
+                (ConfirmPairingCodeDialog) getFragmentController().findDialogByTag(
+                        ConfirmPairingCodeDialog.TAG);
+        if (pairingCodeDialog != null) {
+            pairingCodeDialog.setConfirmPairingCodeListener(mConfirmParingCodeListener);
+        }
+    }
+}
diff --git a/src/com/android/car/settings/security/AddTrustedDeviceProgressFragment.java b/src/com/android/car/settings/security/AddTrustedDeviceProgressFragment.java
new file mode 100644
index 0000000..ae4eaf0
--- /dev/null
+++ b/src/com/android/car/settings/security/AddTrustedDeviceProgressFragment.java
@@ -0,0 +1,56 @@
+/*
+ * 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.car.settings.security;
+
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ProgressBar;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/**
+ * Add trusted device fragment which displays the progress and show the companion app information
+ * to user.
+ */
+public class AddTrustedDeviceProgressFragment extends SettingsFragment {
+    private ProgressBar mProgressBar;
+
+    @Override
+    protected int getPreferenceScreenResId() {
+        return R.xml.add_trusted_device_progress_fragment;
+    }
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        mProgressBar = requireActivity().findViewById(R.id.progress_bar);
+        mProgressBar.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mProgressBar.setVisibility(View.VISIBLE);
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mProgressBar.setVisibility(View.GONE);
+    }
+}
+
diff --git a/src/com/android/car/settings/security/ChooseDeviceInstructionPreferenceController.java b/src/com/android/car/settings/security/ChooseDeviceInstructionPreferenceController.java
new file mode 100644
index 0000000..62d5b66
--- /dev/null
+++ b/src/com/android/car/settings/security/ChooseDeviceInstructionPreferenceController.java
@@ -0,0 +1,49 @@
+/*
+ * 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.car.settings.security;
+
+import android.bluetooth.BluetoothAdapter;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Displays the name of the local Bluetooth adapter.
+ */
+public class ChooseDeviceInstructionPreferenceController extends PreferenceController<Preference> {
+
+    public ChooseDeviceInstructionPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected void updateState(Preference preference) {
+        preference.setTitle(getContext().getString(R.string.trusted_device_select_device,
+                BluetoothAdapter.getDefaultAdapter().getName()));
+    }
+}
diff --git a/src/com/android/car/settings/security/ChooseTrustedDeviceFragment.java b/src/com/android/car/settings/security/ChooseTrustedDeviceFragment.java
index a75f742..f5f976e 100644
--- a/src/com/android/car/settings/security/ChooseTrustedDeviceFragment.java
+++ b/src/com/android/car/settings/security/ChooseTrustedDeviceFragment.java
@@ -24,7 +24,6 @@
  * Fragment which contains trusted device list and add trusted device button.
  */
 public class ChooseTrustedDeviceFragment extends SettingsFragment {
-
     @Override
     @XmlRes
     protected int getPreferenceScreenResId() {
diff --git a/src/com/android/car/settings/security/ConfirmPairingCodeDialog.java b/src/com/android/car/settings/security/ConfirmPairingCodeDialog.java
new file mode 100644
index 0000000..0d91c4b
--- /dev/null
+++ b/src/com/android/car/settings/security/ConfirmPairingCodeDialog.java
@@ -0,0 +1,89 @@
+/*
+ * 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.car.settings.security;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.os.Bundle;
+
+import androidx.fragment.app.DialogFragment;
+
+import com.android.car.settings.R;
+
+/**
+ * Dialog to confirm pairing code.
+ */
+public class ConfirmPairingCodeDialog extends DialogFragment {
+    /** Identifier for the dialog which confirms the pairing code. */
+    public static final String TAG = "confirm_pairing_code_dialog";
+    private static final String DEVICE_NAME_KEY = "deviceName";
+    private static final String PAIRING_CODE_KEY = "pairingCode";
+    private ConfirmPairingCodeListener mConfirmPairingCodeListener;
+
+    /**
+     * Factory method for creating a ConfirmPairingCodeFragment
+     *
+     * @param deviceName  the name of current connected device
+     * @param pairingCode the pairing code sent by the connected device
+     */
+    public static ConfirmPairingCodeDialog newInstance(String deviceName, String pairingCode) {
+        Bundle args = new Bundle();
+        args.putString(DEVICE_NAME_KEY, deviceName);
+        args.putString(PAIRING_CODE_KEY, pairingCode);
+
+        ConfirmPairingCodeDialog dialog = new ConfirmPairingCodeDialog();
+        dialog.setArguments(args);
+        return dialog;
+    }
+
+    /** Sets a listener to act when a user confirms pairing code. */
+    public void setConfirmPairingCodeListener(ConfirmPairingCodeListener listener) {
+        mConfirmPairingCodeListener = listener;
+    }
+
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        Bundle args = getArguments();
+        String deviceName = args.getString(DEVICE_NAME_KEY);
+        String pairingCode = args.getString(PAIRING_CODE_KEY);
+        return new AlertDialog.Builder(getContext())
+                .setTitle(getContext().getString(R.string.trusted_device_pairing_code_dialog_title,
+                        deviceName))
+                .setMessage(pairingCode)
+                .setPositiveButton(R.string.trusted_device_confirm_button, (dialog, which) -> {
+                    if (mConfirmPairingCodeListener != null) {
+                        mConfirmPairingCodeListener.onConfirmPairingCode();
+                    }
+                })
+                .setNegativeButton(android.R.string.cancel, (dialog, which) -> {
+                    if (mConfirmPairingCodeListener != null) {
+                        mConfirmPairingCodeListener.onDialogCancelled();
+                    }
+                })
+                .create();
+    }
+
+    /** A listener for when user interacts with this dialog. */
+    public interface ConfirmPairingCodeListener {
+        /** Defines the actions to take when a user confirms the pairing code. */
+        void onConfirmPairingCode();
+
+        /** Defines the actions to take when a user cancel confirming the pairing code. */
+        void onDialogCancelled();
+    }
+
+}
diff --git a/src/com/android/car/settings/security/TrustedDeviceListPreferenceController.java b/src/com/android/car/settings/security/TrustedDeviceListPreferenceController.java
index 31478e3..016254b 100644
--- a/src/com/android/car/settings/security/TrustedDeviceListPreferenceController.java
+++ b/src/com/android/car/settings/security/TrustedDeviceListPreferenceController.java
@@ -85,16 +85,21 @@
         mPrefs = PreferenceManager.getDefaultSharedPreferences(context);
         mCar = Car.createCar(context);
         try {
-            mCarTrustAgentEnrollmentManager = (CarTrustAgentEnrollmentManager) mCar
-                    .getCarManager(
-                            Car.CAR_TRUST_AGENT_ENROLLMENT_SERVICE);
-            mCarTrustAgentEnrollmentManager.setEnrollmentCallback(mCarTrustAgentEnrollmentCallback);
+            mCarTrustAgentEnrollmentManager = (CarTrustAgentEnrollmentManager) mCar.getCarManager(
+                    Car.CAR_TRUST_AGENT_ENROLLMENT_SERVICE);
         } catch (CarNotConnectedException e) {
             LOG.e(e.getMessage(), e);
         }
     }
 
     @Override
+    protected void checkInitialized() {
+        if (mCarTrustAgentEnrollmentManager == null) {
+            throw new IllegalStateException("mCarTrustAgentEnrollmentManager is null.");
+        }
+    }
+
+    @Override
     protected Class<PreferenceGroup> getPreferenceType() {
         return PreferenceGroup.class;
     }
@@ -112,6 +117,24 @@
         preferenceGroup.setVisible(preferenceGroup.getPreferenceCount() > 0);
     }
 
+    @Override
+    protected void onStartInternal() {
+        try {
+            mCarTrustAgentEnrollmentManager.setEnrollmentCallback(mCarTrustAgentEnrollmentCallback);
+        } catch (CarNotConnectedException e) {
+            LOG.e(e.getMessage(), e);
+        }
+    }
+
+    @Override
+    protected void onStopInternal() {
+        try {
+            mCarTrustAgentEnrollmentManager.setEnrollmentCallback(null);
+        } catch (CarNotConnectedException e) {
+            LOG.e(e.getMessage(), e);
+        }
+    }
+
     /**
      * Method to compare two lists of preferences, used only by updateState method.
      *
diff --git a/tests/robotests/src/com/android/car/settings/security/AddTrustedDevicePreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/security/AddTrustedDevicePreferenceControllerTest.java
new file mode 100644
index 0000000..5c3d437
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/security/AddTrustedDevicePreferenceControllerTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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.car.settings.security;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.verify;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.car.Car;
+import android.car.CarNotConnectedException;
+import android.car.trust.CarTrustAgentEnrollmentManager;
+import android.content.Context;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowCar;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+
+/** Unit tests for {@link AddTrustedDevicePreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowCar.class})
+public class AddTrustedDevicePreferenceControllerTest {
+    private static final String ADDRESS = "00:11:22:33:AA:BB";
+    private Context mContext;
+    private PreferenceControllerTestHelper<AddTrustedDevicePreferenceController>
+            mPreferenceControllerHelper;
+    @Mock
+    private CarTrustAgentEnrollmentManager mMockCarTrustAgentEnrollmentManager;
+    private Preference mPreference;
+    private AddTrustedDevicePreferenceController mController;
+    private BluetoothDevice mBluetoothDevice;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = RuntimeEnvironment.application;
+        ShadowCar.setCarManager(Car.CAR_TRUST_AGENT_ENROLLMENT_SERVICE,
+                mMockCarTrustAgentEnrollmentManager);
+        mPreference = new Preference(mContext);
+        mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
+                AddTrustedDevicePreferenceController.class, mPreference);
+        mController = mPreferenceControllerHelper.getController();
+        mPreferenceControllerHelper.markState(Lifecycle.State.STARTED);
+        mBluetoothDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(ADDRESS);
+    }
+
+    @After
+    public void tearDown() {
+        ShadowCar.reset();
+    }
+
+    @Test
+    public void onPreferenceClicked_startAdvertising() throws CarNotConnectedException {
+        mPreference.performClick();
+        verify(mMockCarTrustAgentEnrollmentManager).startEnrollmentAdvertising();
+    }
+
+    @Test
+    public void onAuthStringAvailable_showDialog() throws CarNotConnectedException {
+        ArgumentCaptor<CarTrustAgentEnrollmentManager.CarTrustAgentEnrollmentCallback>
+                enrollmentCallBack =
+                ArgumentCaptor.forClass(
+                        CarTrustAgentEnrollmentManager.CarTrustAgentEnrollmentCallback.class);
+        verify(mMockCarTrustAgentEnrollmentManager).setEnrollmentCallback(
+                enrollmentCallBack.capture());
+        ArgumentCaptor<CarTrustAgentEnrollmentManager.CarTrustAgentBleCallback>
+                bleCallBack =
+                ArgumentCaptor.forClass(
+                        CarTrustAgentEnrollmentManager.CarTrustAgentBleCallback.class);
+        verify(mMockCarTrustAgentEnrollmentManager).setBleCallback(bleCallBack.capture());
+
+        bleCallBack.getValue().onBleEnrollmentDeviceConnected(mBluetoothDevice);
+        enrollmentCallBack.getValue().onAuthStringAvailable(mBluetoothDevice, "123");
+
+        verify(mPreferenceControllerHelper.getMockFragmentController()).showDialog(
+                any(ConfirmPairingCodeDialog.class), anyString());
+    }
+
+    @Test
+    public void onEscrowTokenActiveStateChanged_returnToListFragment()
+            throws CarNotConnectedException {
+        ArgumentCaptor<CarTrustAgentEnrollmentManager.CarTrustAgentEnrollmentCallback> callBack =
+                ArgumentCaptor.forClass(
+                        CarTrustAgentEnrollmentManager.CarTrustAgentEnrollmentCallback.class);
+        verify(mMockCarTrustAgentEnrollmentManager).setEnrollmentCallback(callBack.capture());
+
+        ArgumentCaptor<CarTrustAgentEnrollmentManager.CarTrustAgentBleCallback>
+                bleCallBack = ArgumentCaptor.forClass(
+                CarTrustAgentEnrollmentManager.CarTrustAgentBleCallback.class);
+        verify(mMockCarTrustAgentEnrollmentManager).setBleCallback(bleCallBack.capture());
+
+        bleCallBack.getValue().onBleEnrollmentDeviceConnected(mBluetoothDevice);
+        callBack.getValue().onEscrowTokenActiveStateChanged(123, true);
+
+        verify(mPreferenceControllerHelper.getMockFragmentController()).goBack();
+    }
+
+    @Test
+    public void onEnrollmentAdvertisingFailed_returnToListFragment()
+            throws CarNotConnectedException {
+        ArgumentCaptor<CarTrustAgentEnrollmentManager.CarTrustAgentBleCallback> callBack =
+                ArgumentCaptor.forClass(
+                        CarTrustAgentEnrollmentManager.CarTrustAgentBleCallback.class);
+        verify(mMockCarTrustAgentEnrollmentManager).setBleCallback(callBack.capture());
+
+        callBack.getValue().onEnrollmentAdvertisingFailed(12);
+
+        verify(mPreferenceControllerHelper.getMockFragmentController()).goBack();
+    }
+
+    @Test
+    public void onBluetoothDeviceConnected_initiateHandshake() throws CarNotConnectedException {
+        ArgumentCaptor<CarTrustAgentEnrollmentManager.CarTrustAgentBleCallback>
+                bleCallBack = ArgumentCaptor.forClass(
+                CarTrustAgentEnrollmentManager.CarTrustAgentBleCallback.class);
+        verify(mMockCarTrustAgentEnrollmentManager).setBleCallback(bleCallBack.capture());
+
+        bleCallBack.getValue().onBleEnrollmentDeviceConnected(mBluetoothDevice);
+
+        verify(mMockCarTrustAgentEnrollmentManager).initiateEnrollmentHandshake(
+                any(BluetoothDevice.class));
+    }
+
+    @Test
+    public void onPairingCodeDialogConfirmed_initiateHandshake()
+            throws CarNotConnectedException {
+        mController.mConfirmParingCodeListener.onConfirmPairingCode();
+        verify(mMockCarTrustAgentEnrollmentManager).enrollmentHandshakeAccepted();
+
+    }
+
+    @Test
+    public void onPairingCodeDialogCancelled_returnToListFragment() {
+        mController.mConfirmParingCodeListener.onDialogCancelled();
+        verify(mPreferenceControllerHelper.getMockFragmentController()).goBack();
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/security/TrustedDeviceListPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/security/TrustedDeviceListPreferenceControllerTest.java
index fc370a2..21bce83 100644
--- a/tests/robotests/src/com/android/car/settings/security/TrustedDeviceListPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/car/settings/security/TrustedDeviceListPreferenceControllerTest.java
@@ -63,7 +63,6 @@
     private PreferenceGroup mPreferenceGroup;
     private SharedPreferences mPrefs;
     private TrustedDeviceListPreferenceController mController;
-    private CarTrustAgentEnrollmentManager.CarTrustAgentEnrollmentCallback mEnrollmentCallBack;
 
     @Before
     public void setUp() {
@@ -81,7 +80,7 @@
         mPreferenceControllerHelper = new PreferenceControllerTestHelper<>(mContext,
                 TrustedDeviceListPreferenceController.class, mPreferenceGroup);
         mController = mPreferenceControllerHelper.getController();
-        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_CREATE);
+        mPreferenceControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
     }
 
     @After
@@ -144,5 +143,4 @@
         assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(1);
         assertThat(mPreferenceGroup.isVisible()).isTrue();
     }
-
 }