Merge "Replace hidden_api_packages with package-info.java and @hide" into main
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 5aa89b9..805cfb7 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3738,6 +3738,7 @@
     method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public android.content.Intent registerReceiverForAllUsers(@Nullable android.content.BroadcastReceiver, @NonNull android.content.IntentFilter, @Nullable String, @Nullable android.os.Handler, int);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, @Nullable android.os.Bundle);
     method public void sendBroadcastMultiplePermissions(@NonNull android.content.Intent, @NonNull String[], @Nullable android.app.BroadcastOptions);
+    method @FlaggedApi("android.os.ordered_broadcast_multiple_permissions") public void sendOrderedBroadcastMultiplePermissions(@NonNull android.content.Intent, @NonNull String[], @Nullable String, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle, @Nullable android.os.Bundle);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @Nullable android.os.Bundle, @NonNull android.os.UserHandle);
     field public static final String AMBIENT_CONTEXT_SERVICE = "ambient_context";
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index af56cb4..d1bd88c 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1524,6 +1524,17 @@
     public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user,
             String receiverPermission, int appOp, Bundle options, BroadcastReceiver resultReceiver,
             Handler scheduler, int initialCode, String initialData, Bundle initialExtras) {
+        String[] receiverPermissions = receiverPermission == null ? null
+                : new String[] {receiverPermission};
+        sendOrderedBroadcastAsUserMultiplePermissions(intent, user, receiverPermissions, appOp,
+                options, resultReceiver, scheduler, initialCode, initialData, initialExtras);
+    }
+
+    @Override
+    public void sendOrderedBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user,
+            String[] receiverPermissions, int appOp, Bundle options,
+            BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
+            String initialData, Bundle initialExtras) {
         IIntentReceiver rd = null;
         if (resultReceiver != null) {
             if (mPackageInfo != null) {
@@ -1543,8 +1554,6 @@
             }
         }
         String resolvedType = intent.resolveTypeIfNeeded(getContentResolver());
-        String[] receiverPermissions = receiverPermission == null ? null
-                : new String[] {receiverPermission};
         try {
             intent.prepareToLeaveProcess(this);
             ActivityManager.getService().broadcastIntentWithFeature(
@@ -1571,6 +1580,20 @@
     }
 
     @Override
+    public void sendOrderedBroadcastMultiplePermissions(Intent intent, String[] receiverPermissions,
+            String receiverAppOp, BroadcastReceiver resultReceiver, Handler scheduler,
+            int initialCode, String initialData, @Nullable Bundle initialExtras,
+            @Nullable Bundle options) {
+        int intAppOp = AppOpsManager.OP_NONE;
+        if (!TextUtils.isEmpty(receiverAppOp)) {
+            intAppOp = AppOpsManager.strOpToOp(receiverAppOp);
+        }
+        sendOrderedBroadcastAsUserMultiplePermissions(intent, getUser(), receiverPermissions,
+                intAppOp, options, resultReceiver, scheduler, initialCode, initialData,
+                initialExtras);
+    }
+
+    @Override
     public void sendOrderedBroadcast(Intent intent, int initialCode, String receiverPermission,
             String receiverAppOp, BroadcastReceiver resultReceiver, Handler scheduler,
             String initialData, @Nullable Bundle initialExtras, Bundle options) {
diff --git a/core/java/android/app/assist/OWNERS b/core/java/android/app/assist/OWNERS
index e4ffd7f..b53bdc2 100644
--- a/core/java/android/app/assist/OWNERS
+++ b/core/java/android/app/assist/OWNERS
@@ -1,2 +1 @@
-hackz@google.com
-volnov@google.com
\ No newline at end of file
+srazdan@google.com
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index a6eed50..b121da3 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2916,6 +2916,23 @@
             @Nullable String initialData, @Nullable  Bundle initialExtras);
 
     /**
+     * Similar to above but takes array of names of permissions that a receiver must hold in order
+     * to receive your broadcast. If empty, no permissions are required.
+     *
+     * @see #sendOrderedBroadcastAsUser(Intent, UserHandle, String,
+     *       BroadcastReceiver, Handler, int, String, Bundle)
+     * @hide
+     */
+    @SuppressWarnings("HiddenAbstractMethod")
+    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+    public void sendOrderedBroadcastAsUserMultiplePermissions(Intent intent,
+            UserHandle user, String[] receiverPermissions, int appOp, Bundle options,
+            BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
+            String initialData, Bundle initialExtras) {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
+
+    /**
      * Version of
      * {@link #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String,
      * Bundle)} that allows you to specify the App Op to enforce restrictions on which receivers
@@ -2997,6 +3014,21 @@
     }
 
     /**
+     * Like {@link #sendOrderedBroadcast(Intent, String, String, BroadcastReceiver, Handler, int,
+     * String, Bundle)}, but also allows specification of a list of multiple permissions.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_ORDERED_BROADCAST_MULTIPLE_PERMISSIONS)
+    @SystemApi
+    public void sendOrderedBroadcastMultiplePermissions(
+            @NonNull Intent intent, @NonNull String[] receiverPermissions,
+            @Nullable String receiverAppOp, @Nullable BroadcastReceiver resultReceiver,
+            @Nullable Handler scheduler, int initialCode, @Nullable String initialData,
+            @Nullable Bundle initialExtras, @Nullable Bundle options) {
+        throw new RuntimeException("Not implemented. Must override in a subclass.");
+    }
+
+    /**
      * <p>Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the
      * Intent you are sending stays around after the broadcast is complete,
      * so that others can quickly retrieve that data through the return
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index a475c29..79fa6ea 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -652,6 +652,16 @@
                 resultReceiver, scheduler, initialCode, initialData, initialExtras);
     }
 
+    /** @hide */
+    @Override
+    public void sendOrderedBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user,
+            @Nullable String[] receiverPermission, int appOp, @Nullable Bundle options,
+            @Nullable BroadcastReceiver resultReceiver, @Nullable Handler scheduler,
+            int initialCode, @Nullable String initialData, @Nullable Bundle initialExtras) {
+        mBase.sendOrderedBroadcastAsUserMultiplePermissions(intent, user, receiverPermission, appOp,
+                options, resultReceiver, scheduler, initialCode, initialData, initialExtras);
+    }
+
     @Override
     public void sendOrderedBroadcast(@RequiresPermission @NonNull Intent intent,
             @Nullable String receiverPermission, @Nullable String receiverAppOp,
@@ -661,6 +671,17 @@
                 scheduler, initialCode, initialData, initialExtras);
     }
 
+    /** @hide */
+    @Override
+    public void sendOrderedBroadcastMultiplePermissions(
+            @NonNull Intent intent, @NonNull String[] receiverPermissions,
+            @Nullable String receiverAppOp, @Nullable BroadcastReceiver resultReceiver,
+            @Nullable Handler scheduler, int initialCode, @Nullable String initialData,
+            @Nullable Bundle initialExtras, @Nullable Bundle options) {
+        mBase.sendOrderedBroadcastMultiplePermissions(intent, receiverPermissions, receiverAppOp,
+                resultReceiver, scheduler, initialCode, initialData, initialExtras, options);
+    }
+
     @Override
     public void sendOrderedBroadcast(@RequiresPermission @NonNull Intent intent, int initialCode,
             @Nullable String receiverPermission, @Nullable String receiverAppOp,
diff --git a/core/java/android/hardware/OWNERS b/core/java/android/hardware/OWNERS
index 51ad151..43d3f54 100644
--- a/core/java/android/hardware/OWNERS
+++ b/core/java/android/hardware/OWNERS
@@ -5,7 +5,7 @@
 sumir@google.com
 
 # Camera
-per-file *Camera*=cychen@google.com,epeev@google.com,etalvala@google.com,shuzhenwang@google.com,zhijunhe@google.com,jchowdhary@google.com
+per-file *Camera*=file:platform/frameworks/av:/camera/OWNERS
 
 # Sensor Privacy
 per-file *SensorPrivacy* = file:platform/frameworks/native:/libs/sensorprivacy/OWNERS
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index ca69457..200c1d8 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -68,6 +68,14 @@
 }
 
 flag {
+    name: "ordered_broadcast_multiple_permissions"
+    is_exported: true
+    namespace: "bluetooth"
+    description: "Guards the Context.sendOrderedBroadcastMultiplePermissions API"
+    bug: "345802719"
+}
+
+flag {
     name: "battery_saver_supported_check_api"
     namespace: "backstage_power"
     description: "Guards a new API in PowerManager to check if battery saver is supported or not."
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index 701d145..85dae63 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -1,6 +1,5 @@
 include /PACKAGE_MANAGER_OWNERS
 
-alanstokes@google.com
 cbrubaker@google.com
 hackbod@android.com
 hackbod@google.com
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 0f3f807..73ca6fb 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -436,8 +436,13 @@
         if (mBluetoothHeadset == null || mBluetoothHeadsetDevice == null) {
             return false;
         }
-        return mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
-                == BluetoothHeadset.STATE_AUDIO_CONNECTED;
+        try {
+            return mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+                    == BluetoothHeadset.STATE_AUDIO_CONNECTED;
+        } catch (Exception e) {
+            Log.e(TAG, "Exception while getting audio state of " + mBluetoothHeadsetDevice, e);
+        }
+        return false;
     }
 
     // @GuardedBy("mDeviceBroker.mSetModeLock")
@@ -1051,12 +1056,16 @@
     }
 
     private void checkScoAudioState() {
-        if (mBluetoothHeadset != null
-                && mBluetoothHeadsetDevice != null
-                && mScoAudioState == SCO_STATE_INACTIVE
-                && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
-                != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
-            mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+        try {
+            if (mBluetoothHeadset != null
+                    && mBluetoothHeadsetDevice != null
+                    && mScoAudioState == SCO_STATE_INACTIVE
+                    && mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+                    != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+                mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "Exception while getting audio state of " + mBluetoothHeadsetDevice, e);
         }
     }
 
diff --git a/services/core/java/com/android/server/location/altitude/AltitudeService.java b/services/core/java/com/android/server/location/altitude/AltitudeService.java
index 289d4a2..96540c2 100644
--- a/services/core/java/com/android/server/location/altitude/AltitudeService.java
+++ b/services/core/java/com/android/server/location/altitude/AltitudeService.java
@@ -25,6 +25,7 @@
 import android.location.Location;
 import android.location.altitude.AltitudeConverter;
 import android.os.RemoteException;
+import android.util.Log;
 
 import com.android.server.SystemService;
 
@@ -38,6 +39,8 @@
  */
 public class AltitudeService extends IAltitudeService.Stub {
 
+    private static final String TAG = "AltitudeService";
+
     private final AltitudeConverter mAltitudeConverter = new AltitudeConverter();
     private final Context mContext;
 
@@ -59,6 +62,7 @@
         try {
             mAltitudeConverter.addMslAltitudeToLocation(mContext, location);
         } catch (IOException e) {
+            Log.e(TAG, "", e);
             response.success = false;
             return response;
         }
@@ -74,6 +78,7 @@
         try {
             return mAltitudeConverter.getGeoidHeight(mContext, request);
         } catch (IOException e) {
+            Log.e(TAG, "", e);
             GetGeoidHeightResponse response = new GetGeoidHeightResponse();
             response.success = false;
             return response;
diff --git a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
index 7aec42b..00daf41 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -43,6 +43,7 @@
 import android.Manifest;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
 import android.app.AlarmManager;
 import android.app.IActivityManager;
 import android.app.admin.DevicePolicyManager;
@@ -142,6 +143,7 @@
     private final Map<ComponentName, ITrustAgentService.Stub> mMockTrustAgents = new HashMap<>();
 
     private @Mock ActivityManager mActivityManager;
+    private @Mock ActivityManagerInternal mActivityManagerInternal;
     private @Mock AlarmManager mAlarmManager;
     private @Mock BiometricManager mBiometricManager;
     private @Mock DevicePolicyManager mDevicePolicyManager;
@@ -158,6 +160,7 @@
     private HandlerThread mHandlerThread;
     private TrustManagerService mService;
     private ITrustManager mTrustManager;
+    private ActivityManagerInternal mPreviousActivityManagerInternal;
 
     @Before
     public void setUp() throws Exception {
@@ -210,6 +213,11 @@
         mMockContext.setMockPackageManager(mPackageManager);
         mMockContext.addMockSystemService(UserManager.class, mUserManager);
         doReturn(mWindowManager).when(() -> WindowManagerGlobal.getWindowManagerService());
+        mPreviousActivityManagerInternal = LocalServices.getService(
+                ActivityManagerInternal.class);
+        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+        LocalServices.addService(ActivityManagerInternal.class,
+                mActivityManagerInternal);
         LocalServices.addService(SystemServiceManager.class, mock(SystemServiceManager.class));
 
         grantPermission(Manifest.permission.ACCESS_KEYGUARD_SECURE_STORAGE);
@@ -257,7 +265,14 @@
     @After
     public void tearDown() {
         LocalServices.removeServiceForTest(SystemServiceManager.class);
-        mHandlerThread.quit();
+        LocalServices.removeServiceForTest(ActivityManagerInternal.class);
+        if (mPreviousActivityManagerInternal != null) {
+            LocalServices.addService(ActivityManagerInternal.class,
+                    mPreviousActivityManagerInternal);
+        }
+        if (mHandlerThread != null) {
+            mHandlerThread.quit();
+        }
     }
 
     @Test
diff --git a/test-mock/src/android/test/mock/MockContext.java b/test-mock/src/android/test/mock/MockContext.java
index cf38bea..6bf7ff5 100644
--- a/test-mock/src/android/test/mock/MockContext.java
+++ b/test-mock/src/android/test/mock/MockContext.java
@@ -479,6 +479,15 @@
         throw new UnsupportedOperationException();
     }
 
+    /** @hide */
+    @Override
+    public void sendOrderedBroadcastAsUserMultiplePermissions(Intent intent, UserHandle user,
+            String[] receiverPermissions, int appOp, Bundle options,
+            BroadcastReceiver resultReceiver, Handler scheduler, int initialCode,
+            String initialData, Bundle initialExtras) {
+        throw new UnsupportedOperationException();
+    }
+
     @Override
     public void sendOrderedBroadcast(Intent intent, String receiverPermission,
             String receiverAppOp, BroadcastReceiver resultReceiver, Handler scheduler,
diff --git a/tests/TrustTests/AndroidManifest.xml b/tests/TrustTests/AndroidManifest.xml
index 30cf345..2f6c0dd 100644
--- a/tests/TrustTests/AndroidManifest.xml
+++ b/tests/TrustTests/AndroidManifest.xml
@@ -78,6 +78,7 @@
                 <action android:name="android.service.trust.TrustAgentService" />
             </intent-filter>
         </service>
+
         <service
             android:name=".IsActiveUnlockRunningTrustAgent"
             android:exported="true"
@@ -88,6 +89,16 @@
             </intent-filter>
         </service>
 
+        <service
+            android:name=".UnlockAttemptTrustAgent"
+            android:exported="true"
+            android:label="Test Agent"
+            android:permission="android.permission.BIND_TRUST_AGENT">
+            <intent-filter>
+                <action android:name="android.service.trust.TrustAgentService" />
+            </intent-filter>
+        </service>
+
     </application>
 
     <!--  self-instrumenting test package. -->
diff --git a/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt b/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt
new file mode 100644
index 0000000..2c9361d
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2024 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.trust.test
+
+import android.app.trust.TrustManager
+import android.content.Context
+import android.trust.BaseTrustAgentService
+import android.trust.TrustTestActivity
+import android.trust.test.lib.LockStateTrackingRule
+import android.trust.test.lib.ScreenLockRule
+import android.trust.test.lib.TestTrustListener
+import android.trust.test.lib.TrustAgentRule
+import android.util.Log
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.runner.RunWith
+
+/**
+ * Test for the impacts of reporting unlock attempts.
+ *
+ * atest TrustTests:UnlockAttemptTest
+ */
+@RunWith(AndroidJUnit4::class)
+class UnlockAttemptTest {
+    private val context = getApplicationContext<Context>()
+    private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager
+    private val userId = context.userId
+    private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java)
+    private val screenLockRule = ScreenLockRule(requireStrongAuth = true)
+    private val lockStateTrackingRule = LockStateTrackingRule()
+    private val trustAgentRule =
+        TrustAgentRule<UnlockAttemptTrustAgent>(startUnlocked = false, startEnabled = false)
+
+    private val trustListener = UnlockAttemptTrustListener()
+    private val agent get() = trustAgentRule.agent
+
+    @get:Rule
+    val rule: RuleChain =
+        RuleChain.outerRule(activityScenarioRule)
+            .around(screenLockRule)
+            .around(lockStateTrackingRule)
+            .around(trustAgentRule)
+
+    @Before
+    fun setUp() {
+        trustManager.registerTrustListener(trustListener)
+    }
+
+    @Test
+    fun successfulUnlockAttempt_allowsTrustAgentToStart() =
+        runUnlockAttemptTest(enableAndVerifyTrustAgent = false, managingTrust = false) {
+            trustAgentRule.enableTrustAgent()
+
+            triggerSuccessfulUnlock()
+
+            trustAgentRule.verifyAgentIsRunning(MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START)
+        }
+
+    @Test
+    fun successfulUnlockAttempt_notifiesTrustAgent() =
+        runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) {
+            val oldSuccessfulCount = agent.successfulUnlockCallCount
+            val oldFailedCount = agent.failedUnlockCallCount
+
+            triggerSuccessfulUnlock()
+
+            assertThat(agent.successfulUnlockCallCount).isEqualTo(oldSuccessfulCount + 1)
+            assertThat(agent.failedUnlockCallCount).isEqualTo(oldFailedCount)
+        }
+
+    @Test
+    fun successfulUnlockAttempt_notifiesTrustListenerOfManagedTrust() =
+        runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) {
+            val oldTrustManagedChangedCount = trustListener.onTrustManagedChangedCount[userId] ?: 0
+
+            triggerSuccessfulUnlock()
+
+            assertThat(trustListener.onTrustManagedChangedCount[userId] ?: 0).isEqualTo(
+                oldTrustManagedChangedCount + 1
+            )
+        }
+
+    @Test
+    fun failedUnlockAttempt_doesNotAllowTrustAgentToStart() =
+        runUnlockAttemptTest(enableAndVerifyTrustAgent = false, managingTrust = false) {
+            trustAgentRule.enableTrustAgent()
+
+            triggerFailedUnlock()
+
+            trustAgentRule.ensureAgentIsNotRunning(MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START)
+        }
+
+    @Test
+    fun failedUnlockAttempt_notifiesTrustAgent() =
+        runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) {
+            val oldSuccessfulCount = agent.successfulUnlockCallCount
+            val oldFailedCount = agent.failedUnlockCallCount
+
+            triggerFailedUnlock()
+
+            assertThat(agent.successfulUnlockCallCount).isEqualTo(oldSuccessfulCount)
+            assertThat(agent.failedUnlockCallCount).isEqualTo(oldFailedCount + 1)
+        }
+
+    @Test
+    fun failedUnlockAttempt_doesNotNotifyTrustListenerOfManagedTrust() =
+        runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) {
+            val oldTrustManagedChangedCount = trustListener.onTrustManagedChangedCount[userId] ?: 0
+
+            triggerFailedUnlock()
+
+            assertThat(trustListener.onTrustManagedChangedCount[userId] ?: 0).isEqualTo(
+                oldTrustManagedChangedCount
+            )
+        }
+
+    private fun runUnlockAttemptTest(
+        enableAndVerifyTrustAgent: Boolean,
+        managingTrust: Boolean,
+        testBlock: () -> Unit,
+    ) {
+        if (enableAndVerifyTrustAgent) {
+            Log.i(TAG, "Triggering successful unlock")
+            triggerSuccessfulUnlock()
+            Log.i(TAG, "Enabling and waiting for trust agent")
+            trustAgentRule.enableAndVerifyTrustAgentIsRunning(
+                MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START
+            )
+            Log.i(TAG, "Managing trust: $managingTrust")
+            agent.setManagingTrust(managingTrust)
+            await()
+        }
+        testBlock()
+    }
+
+    private fun triggerSuccessfulUnlock() {
+        screenLockRule.successfulScreenLockAttempt()
+        trustAgentRule.reportSuccessfulUnlock()
+        await()
+    }
+
+    private fun triggerFailedUnlock() {
+        screenLockRule.failedScreenLockAttempt()
+        trustAgentRule.reportFailedUnlock()
+        await()
+    }
+
+    companion object {
+        private const val TAG = "UnlockAttemptTest"
+        private fun await(millis: Long = 500) = Thread.sleep(millis)
+        private const val MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START = 10000L
+    }
+}
+
+class UnlockAttemptTrustAgent : BaseTrustAgentService() {
+    var successfulUnlockCallCount: Long = 0
+        private set
+    var failedUnlockCallCount: Long = 0
+        private set
+
+    override fun onUnlockAttempt(successful: Boolean) {
+        super.onUnlockAttempt(successful)
+        if (successful) {
+            successfulUnlockCallCount++
+        } else {
+            failedUnlockCallCount++
+        }
+    }
+}
+
+private class UnlockAttemptTrustListener : TestTrustListener() {
+    var enabledTrustAgentsChangedCount = mutableMapOf<Int, Int>()
+    var onTrustManagedChangedCount = mutableMapOf<Int, Int>()
+
+    override fun onEnabledTrustAgentsChanged(userId: Int) {
+        enabledTrustAgentsChangedCount.compute(userId) { _: Int, curr: Int? ->
+            if (curr == null) 0 else curr + 1
+        }
+    }
+
+    data class TrustChangedParams(
+        val enabled: Boolean,
+        val newlyUnlocked: Boolean,
+        val userId: Int,
+        val flags: Int,
+        val trustGrantedMessages: MutableList<String>?
+    )
+
+    val onTrustChangedCalls = mutableListOf<TrustChangedParams>()
+
+    override fun onTrustChanged(
+        enabled: Boolean,
+        newlyUnlocked: Boolean,
+        userId: Int,
+        flags: Int,
+        trustGrantedMessages: MutableList<String>
+    ) {
+        onTrustChangedCalls += TrustChangedParams(
+            enabled, newlyUnlocked, userId, flags, trustGrantedMessages
+        )
+    }
+
+    override fun onTrustManagedChanged(enabled: Boolean, userId: Int) {
+        onTrustManagedChangedCount.compute(userId) { _: Int, curr: Int? ->
+            if (curr == null) 0 else curr + 1
+        }
+    }
+}
diff --git a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
index f1edca3..1ccdcc6 100644
--- a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
+++ b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
@@ -24,6 +24,8 @@
 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
 import androidx.test.uiautomator.UiDevice
 import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
 import com.android.internal.widget.LockscreenCredential
 import com.google.common.truth.Truth.assertWithMessage
 import org.junit.rules.TestRule
@@ -32,13 +34,18 @@
 
 /**
  * Sets a screen lock on the device for the duration of the test.
+ *
+ * @param requireStrongAuth Whether a strong auth is required at the beginning.
+ * If true, trust agents will not be available until the user verifies their credentials.
  */
-class ScreenLockRule : TestRule {
+class ScreenLockRule(val requireStrongAuth: Boolean = false) : TestRule {
     private val context: Context = getApplicationContext()
+    private val userId = context.userId
     private val uiDevice = UiDevice.getInstance(getInstrumentation())
     private val windowManager = checkNotNull(WindowManagerGlobal.getWindowManagerService())
     private val lockPatternUtils = LockPatternUtils(context)
     private var instantLockSavedValue = false
+    private var strongAuthSavedValue: Int = 0
 
     override fun apply(base: Statement, description: Description) = object : Statement() {
         override fun evaluate() {
@@ -46,10 +53,12 @@
             dismissKeyguard()
             setScreenLock()
             setLockOnPowerButton()
+            configureStrongAuthState()
 
             try {
                 base.evaluate()
             } finally {
+                restoreStrongAuthState()
                 removeScreenLock()
                 revertLockOnPowerButton()
                 dismissKeyguard()
@@ -57,6 +66,22 @@
         }
     }
 
+    private fun configureStrongAuthState() {
+        strongAuthSavedValue = lockPatternUtils.getStrongAuthForUser(userId)
+        if (requireStrongAuth) {
+            Log.d(TAG, "Triggering strong auth due to simulated lockdown")
+            lockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, userId)
+            wait("strong auth required after lockdown") {
+                lockPatternUtils.getStrongAuthForUser(userId) ==
+                        STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
+            }
+        }
+    }
+
+    private fun restoreStrongAuthState() {
+        lockPatternUtils.requireStrongAuth(strongAuthSavedValue, userId)
+    }
+
     private fun verifyNoScreenLockAlreadySet() {
         assertWithMessage("Screen Lock must not already be set on device")
                 .that(lockPatternUtils.isSecure(context.userId))
@@ -82,6 +107,22 @@
         }
     }
 
+    fun successfulScreenLockAttempt() {
+        lockPatternUtils.verifyCredential(LockscreenCredential.createPin(PIN), context.userId, 0)
+        lockPatternUtils.userPresent(context.userId)
+        wait("strong auth not required") {
+            lockPatternUtils.getStrongAuthForUser(context.userId) == STRONG_AUTH_NOT_REQUIRED
+        }
+    }
+
+    fun failedScreenLockAttempt() {
+        lockPatternUtils.verifyCredential(
+            LockscreenCredential.createPin(WRONG_PIN),
+            context.userId,
+            0
+        )
+    }
+
     private fun setScreenLock() {
         lockPatternUtils.setLockCredential(
                 LockscreenCredential.createPin(PIN),
@@ -121,5 +162,6 @@
     companion object {
         private const val TAG = "ScreenLockRule"
         private const val PIN = "0000"
+        private const val WRONG_PIN = "0001"
     }
 }
diff --git a/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt
index 18bc029..404c6d9 100644
--- a/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt
+++ b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt
@@ -20,14 +20,15 @@
 import android.content.ComponentName
 import android.content.Context
 import android.trust.BaseTrustAgentService
+import android.trust.test.lib.TrustAgentRule.Companion.invoke
 import android.util.Log
 import androidx.test.core.app.ApplicationProvider.getApplicationContext
 import com.android.internal.widget.LockPatternUtils
 import com.google.common.truth.Truth.assertWithMessage
+import kotlin.reflect.KClass
 import org.junit.rules.TestRule
 import org.junit.runner.Description
 import org.junit.runners.model.Statement
-import kotlin.reflect.KClass
 
 /**
  * Enables a trust agent and causes the system service to bind to it.
@@ -37,7 +38,9 @@
  * @constructor Creates the rule. Do not use; instead, use [invoke].
  */
 class TrustAgentRule<T : BaseTrustAgentService>(
-    private val serviceClass: KClass<T>
+    private val serviceClass: KClass<T>,
+    private val startUnlocked: Boolean,
+    private val startEnabled: Boolean,
 ) : TestRule {
     private val context: Context = getApplicationContext()
     private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager
@@ -48,11 +51,18 @@
     override fun apply(base: Statement, description: Description) = object : Statement() {
         override fun evaluate() {
             verifyTrustServiceRunning()
-            unlockDeviceWithCredential()
-            enableTrustAgent()
+            if (startUnlocked) {
+                reportSuccessfulUnlock()
+            } else {
+                Log.i(TAG, "Trust manager not starting in unlocked state")
+            }
 
             try {
-                verifyAgentIsRunning()
+                if (startEnabled) {
+                    enableAndVerifyTrustAgentIsRunning()
+                } else {
+                    Log.i(TAG, "Trust agent ${serviceClass.simpleName} not enabled")
+                }
                 base.evaluate()
             } finally {
                 disableTrustAgent()
@@ -64,12 +74,22 @@
         assertWithMessage("Trust service is not running").that(trustManager).isNotNull()
     }
 
-    private fun unlockDeviceWithCredential() {
-        Log.d(TAG, "Unlocking device with credential")
+    fun reportSuccessfulUnlock() {
+        Log.i(TAG, "Reporting successful unlock")
         trustManager.reportUnlockAttempt(true, context.userId)
     }
 
-    private fun enableTrustAgent() {
+    fun reportFailedUnlock() {
+        Log.i(TAG, "Reporting failed unlock")
+        trustManager.reportUnlockAttempt(false, context.userId)
+    }
+
+    fun enableAndVerifyTrustAgentIsRunning(maxWait: Long = 30000L) {
+        enableTrustAgent()
+        verifyAgentIsRunning(maxWait)
+    }
+
+    fun enableTrustAgent() {
         val componentName = ComponentName(context, serviceClass.java)
         val userId = context.userId
         Log.i(TAG, "Enabling trust agent ${componentName.flattenToString()} for user $userId")
@@ -79,12 +99,18 @@
         lockPatternUtils.setEnabledTrustAgents(agents, userId)
     }
 
-    private fun verifyAgentIsRunning() {
-        wait("${serviceClass.simpleName} to be running") {
+    fun verifyAgentIsRunning(maxWait: Long = 30000L) {
+        wait("${serviceClass.simpleName} to be running", maxWait) {
             BaseTrustAgentService.instance(serviceClass) != null
         }
     }
 
+    fun ensureAgentIsNotRunning(window: Long = 30000L) {
+        ensure("${serviceClass.simpleName} is not running", window) {
+            BaseTrustAgentService.instance(serviceClass) == null
+        }
+    }
+
     private fun disableTrustAgent() {
         val componentName = ComponentName(context, serviceClass.java)
         val userId = context.userId
@@ -97,13 +123,23 @@
 
     companion object {
         /**
-         * Creates a new rule for the specified agent class. Example usage:
+         * Creates a new rule for the specified agent class. Starts with the device unlocked and
+         * the trust agent enabled. Example usage:
          * ```
          *   @get:Rule val rule = TrustAgentRule<MyTestAgent>()
          * ```
+         *
+         * Also supports setting different device lock and trust agent enablement states:
+         * ```
+         *   @get:Rule val rule = TrustAgentRule<MyTestAgent>(startUnlocked = false, startEnabled = false)
+         * ```
          */
-        inline operator fun <reified T : BaseTrustAgentService> invoke() =
-            TrustAgentRule(T::class)
+        inline operator fun <reified T : BaseTrustAgentService> invoke(
+            startUnlocked: Boolean = true,
+            startEnabled: Boolean = true,
+        ) =
+            TrustAgentRule(T::class, startUnlocked, startEnabled)
+
 
         private const val TAG = "TrustAgentRule"
     }
diff --git a/tests/TrustTests/src/android/trust/test/lib/utils.kt b/tests/TrustTests/src/android/trust/test/lib/Utils.kt
similarity index 63%
rename from tests/TrustTests/src/android/trust/test/lib/utils.kt
rename to tests/TrustTests/src/android/trust/test/lib/Utils.kt
index e047202..3b32b47 100644
--- a/tests/TrustTests/src/android/trust/test/lib/utils.kt
+++ b/tests/TrustTests/src/android/trust/test/lib/Utils.kt
@@ -39,7 +39,7 @@
 ) {
     var waited = 0L
     var count = 0
-    while (!conditionFunction.invoke(count)) {
+    while (!conditionFunction(count)) {
         assertWithMessage("Condition exceeded maximum wait time of $maxWait ms: $description")
             .that(waited <= maxWait)
             .isTrue()
@@ -49,3 +49,34 @@
         Thread.sleep(rate)
     }
 }
+
+/**
+ * Ensures that [conditionFunction] is true with a failed assertion if it is not within [window]
+ * ms.
+ *
+ * The condition function can perform additional logic (for example, logging or attempting to make
+ * the condition become true).
+ *
+ * @param conditionFunction function which takes the attempt count & returns whether the condition
+ *                          is met
+ */
+internal fun ensure(
+    description: String? = null,
+    window: Long = 30000L,
+    rate: Long = 50L,
+    conditionFunction: (count: Int) -> Boolean
+) {
+    var waited = 0L
+    var count = 0
+    while (waited <= window) {
+        assertWithMessage("Condition failed within $window ms: $description").that(
+                conditionFunction(
+                    count
+                )
+            ).isTrue()
+        waited += rate
+        count++
+        Log.i(TAG, "Ensuring $description ($waited/$window) #$count")
+        Thread.sleep(rate)
+    }
+}