Move and rename Bluetooth profile inhibits to CarProjectionManager.

Move the "temporary Bluetooth profile disconnect" functionality,
previously available on CarBluetoothManager, to CarProjectionManager
under the name "Bluetooth profile inhibits". This makes it available
as a @SystemApi guarded by PERMISSION_CAR_PROJECTION.

Also, update implementation in BluetoothDeviceConnectionPolicy to
reflect the new name, and fix a small logic bug where newly-paired
devices would not be reenabled as expected.

Bug: 116226107
Test: manual test of inhibit/uninhibit with projection app
Change-Id: Iac6b72fd72156ef221228b73b24cc5c0eb421b8c
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index 1c34646..9ab0716 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -46,6 +46,8 @@
     method public void onCarDisconnected();
     method public void registerProjectionListener(android.car.CarProjectionManager.CarProjectionListener, int) throws android.car.CarNotConnectedException;
     method public void registerProjectionRunner(android.content.Intent) throws android.car.CarNotConnectedException;
+    method public boolean releaseBluetoothProfileInhibit(android.bluetooth.BluetoothDevice, int, android.os.IBinder);
+    method public boolean requestBluetoothProfileInhibit(android.bluetooth.BluetoothDevice, int, android.os.IBinder);
     method public void startProjectionAccessPoint(android.car.CarProjectionManager.ProjectionAccessPointCallback);
     method public void stopProjectionAccessPoint();
     method public void unregisterProjectionListener();
diff --git a/car-lib/src/android/car/CarBluetoothManager.java b/car-lib/src/android/car/CarBluetoothManager.java
index bb9c8ff..e6b9161 100644
--- a/car-lib/src/android/car/CarBluetoothManager.java
+++ b/car-lib/src/android/car/CarBluetoothManager.java
@@ -128,48 +128,6 @@
         }
     }
 
-    /**
-     * Request to disconnect the given profile on the given device, and prevent it from reconnecting
-     * until either the request is released, or the process owning the given token dies.
-     *
-     * @param device The device on which to disconnect a profile.
-     * @param profile The {@link android.bluetooth.BluetoothProfile} to disconnect.
-     * @param token A {@link IBinder} to be used as an identity for the request. If the process
-     *     owning the token dies, the request will automatically be released.
-     * @return True if the profile was successfully disconnected, false if an error occurred.
-     */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
-    public boolean requestTemporaryProfileDisconnect(
-            BluetoothDevice device, int profile, IBinder token) throws CarNotConnectedException {
-        try {
-            return mService.requestTemporaryDisconnect(device, profile, token);
-        } catch (RemoteException e) {
-            Log.e(CarLibLog.TAG_CAR, "requestTemporaryDisconnect failed", e);
-            throw new CarNotConnectedException(e);
-        }
-    }
-
-    /**
-     * Undo a previous call to {@link #requestTemporaryProfileDisconnect} with the same parameters,
-     * and reconnect the profile if no other requests are active.
-     *
-     * @param device The device on which to release the disconnect request.
-     * @param profile The profile on which to release the disconnect request.
-     * @param token The token provided in the original call to
-     *              {@link #requestTemporaryProfileDisconnect}.
-     *
-     * @return True if the request was released, false if an error occurred.
-     */
-    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
-    public boolean releaseTemporaryProfileDisconnect(
-            BluetoothDevice device, int profile, IBinder token) throws CarNotConnectedException {
-        try {
-            return mService.releaseTemporaryDisconnect(device, profile, token);
-        } catch (RemoteException e) {
-            Log.e(CarLibLog.TAG_CAR, "requestTemporaryDisconnect failed", e);
-            throw new CarNotConnectedException(e);
-        }
-    }
 
     /** @hide */
     public CarBluetoothManager(IBinder service, Context context) {
diff --git a/car-lib/src/android/car/CarProjectionManager.java b/car-lib/src/android/car/CarProjectionManager.java
index 2fc5944..a5a25b5 100644
--- a/car-lib/src/android/car/CarProjectionManager.java
+++ b/car-lib/src/android/car/CarProjectionManager.java
@@ -17,6 +17,7 @@
 package android.car;
 
 import android.annotation.SystemApi;
+import android.bluetooth.BluetoothDevice;
 import android.content.Intent;
 import android.net.wifi.WifiConfiguration;
 import android.os.Binder;
@@ -237,6 +238,46 @@
     }
 
     /**
+     * Request to disconnect the given profile on the given device, and prevent it from reconnecting
+     * until either the request is released, or the process owning the given token dies.
+     *
+     * @param device  The device on which to inhibit a profile.
+     * @param profile The {@link android.bluetooth.BluetoothProfile} to inhibit.
+     * @param token   A {@link IBinder} to be used as an identity for the request. If the process
+     *                owning the token dies, the request will automatically be released.
+     * @return True if the profile was successfully inhibited, false if an error occurred.
+     */
+    public boolean requestBluetoothProfileInhibit(
+            BluetoothDevice device, int profile, IBinder token) {
+        try {
+            return mService.requestBluetoothProfileInhibit(device, profile, token);
+        } catch (RemoteException e) {
+            Log.e(CarLibLog.TAG_CAR, "requestBluetoothProfileInhibit failed", e);
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Release an inhibit request made by {@link #requestBluetoothProfileInhibit}, and reconnect the
+     * profile if no other inhibit requests are active.
+     *
+     * @param device  The device on which to release the inhibit request.
+     * @param profile The profile on which to release the inhibit request.
+     * @param token   The token provided in the original call to
+     *                {@link #requestBluetoothProfileInhibit}.
+     * @return True if the request was released, false if an error occurred.
+     */
+    public boolean releaseBluetoothProfileInhibit(
+            BluetoothDevice device, int profile, IBinder token) {
+        try {
+            return mService.releaseBluetoothProfileInhibit(device, profile, token);
+        } catch (RemoteException e) {
+            Log.e(CarLibLog.TAG_CAR, "releaseBluetoothProfileInhibit failed", e);
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Callback class for applications to receive updates about the LocalOnlyHotspot status.
      */
     public abstract static class ProjectionAccessPointCallback {
diff --git a/car-lib/src/android/car/ICarBluetooth.aidl b/car-lib/src/android/car/ICarBluetooth.aidl
index ee7eb29..a5fbb73 100644
--- a/car-lib/src/android/car/ICarBluetooth.aidl
+++ b/car-lib/src/android/car/ICarBluetooth.aidl
@@ -24,6 +24,4 @@
     void clearBluetoothDeviceConnectionPriority(in int profileToClear,in int priorityToClear);
     boolean isPriorityDevicePresent(in int profile, in int priorityToCheck);
     String getDeviceNameWithPriority(in int profile, in int priorityToCheck);
-    boolean requestTemporaryDisconnect(in BluetoothDevice device, in int profile, in IBinder token);
-    boolean releaseTemporaryDisconnect(in BluetoothDevice device, in int profile, in IBinder token);
 }
diff --git a/car-lib/src/android/car/ICarProjection.aidl b/car-lib/src/android/car/ICarProjection.aidl
index 4f80e6c..efaac0d 100644
--- a/car-lib/src/android/car/ICarProjection.aidl
+++ b/car-lib/src/android/car/ICarProjection.aidl
@@ -16,6 +16,7 @@
 
 package android.car;
 
+import android.bluetooth.BluetoothDevice;
 import android.car.ICarProjectionCallback;
 import android.content.Intent;
 import android.os.Messenger;
@@ -60,4 +61,12 @@
      * Stops previously requested Wi-Fi access point.
      */
     void stopProjectionAccessPoint(in IBinder binder) = 5;
+
+    /** Disconnect a Bluetooth profile, and prevent it from reconnecting. */
+    boolean requestBluetoothProfileInhibit(
+            in BluetoothDevice device, in int profile, in IBinder token) = 6;
+
+    /** Undo the effects of requestBluetoothProfileInhibit. */
+    boolean releaseBluetoothProfileInhibit(
+            in BluetoothDevice device, in int profile, in IBinder token) = 7;
 }
diff --git a/car-lib/src/android/car/settings/CarSettings.java b/car-lib/src/android/car/settings/CarSettings.java
index f614601..f89f521 100644
--- a/car-lib/src/android/car/settings/CarSettings.java
+++ b/car-lib/src/android/car/settings/CarSettings.java
@@ -206,7 +206,7 @@
          * Read and written by {@link com.android.car.BluetoothDeviceConnectionPolicy}.
          * @hide
          */
-        public static final String KEY_BLUETOOTH_TEMPORARY_DISCONNECTS =
-                "android.car.BLUETOOTH_TEMPORARY_DISCONNECTS";
+        public static final String KEY_BLUETOOTH_PROFILES_INHIBITED =
+                "android.car.BLUETOOTH_PROFILES_INHIBITED";
     }
 }
diff --git a/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java b/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
index dcd7e16..5d7fc0e 100644
--- a/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
+++ b/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
@@ -20,7 +20,7 @@
 import static android.car.settings.CarSettings.Secure.KEY_BLUETOOTH_AUTOCONNECT_MUSIC_DEVICES;
 import static android.car.settings.CarSettings.Secure.KEY_BLUETOOTH_AUTOCONNECT_NETWORK_DEVICES;
 import static android.car.settings.CarSettings.Secure.KEY_BLUETOOTH_AUTOCONNECT_PHONE_DEVICES;
-import static android.car.settings.CarSettings.Secure.KEY_BLUETOOTH_TEMPORARY_DISCONNECTS;
+import static android.car.settings.CarSettings.Secure.KEY_BLUETOOTH_PROFILES_INHIBITED;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -59,6 +59,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.io.PrintWriter;
@@ -100,7 +101,7 @@
     private static final String SETTINGS_DELIMITER = ",";
     private static final boolean DBG = Utils.DBG;
 
-    private static final Binder RESTORED_TEMPORARY_DISCONNECT_TOKEN = new Binder();
+    private static final Binder RESTORED_PROFILE_INHIBIT_TOKEN = new Binder();
     private static final long RESTORE_BACKOFF_MILLIS = 1000L;
 
     private final Context mContext;
@@ -158,9 +159,13 @@
     // Maintain a list of Paired devices which haven't connected on any profiles yet.
     private Set<BluetoothDevice> mPairedButUnconnectedDevices = new HashSet<>();
 
-    // State for temporary disconnects. Guarded by lock on `this`.
-    private final SetMultimap<ConnectionParams, DisconnectRecord> mTemporaryDisconnects;
-    private final HashSet<DisconnectRecord> mRestoredDisconnects = new HashSet<>();
+    // State for profile inhibits.
+    @GuardedBy("this")
+    private final SetMultimap<ConnectionParams, InhibitRecord> mProfileInhibits =
+            new SetMultimap<>();
+    @GuardedBy("this")
+    private final HashSet<InhibitRecord> mRestoredInhibits = new HashSet<>();
+    @GuardedBy("this")
     private final HashSet<ConnectionParams> mAlreadyDisabledProfiles = new HashSet<>();
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -227,8 +232,6 @@
             Log.w(TAG, "No Bluetooth Adapter Available");
         }
         mFastPairProvider = new FastPairProvider(mContext);
-
-        mTemporaryDisconnects = new SetMultimap<>();
     }
 
     /**
@@ -331,13 +334,13 @@
         }
     }
 
-    private class DisconnectRecord implements IBinder.DeathRecipient {
+    private class InhibitRecord implements IBinder.DeathRecipient {
         private final ConnectionParams mParams;
         private final IBinder mToken;
 
         private boolean mRemoved = false;
 
-        DisconnectRecord(ConnectionParams params, IBinder token) {
+        InhibitRecord(ConnectionParams params, IBinder token) {
             this.mParams = params;
             this.mToken = token;
         }
@@ -356,7 +359,7 @@
                     return true;
                 }
 
-                if (removeDisconnectRecord(this)) {
+                if (removeInhibitRecord(this)) {
                     mRemoved = true;
                     return true;
                 } else {
@@ -368,7 +371,7 @@
         @Override
         public void binderDied() {
             if (DBG) {
-                Log.d(TAG, "Releasing disconnect request on profile "
+                Log.d(TAG, "Releasing inhibit request on profile "
                         + Utils.getProfileName(mParams.getBluetoothProfile())
                         + " for device " + mParams.getBluetoothDevice()
                         + ": requesting process died");
@@ -497,15 +500,19 @@
         }
         if (mCarBluetoothUserService != null) {
             for (Integer profile : mProfilesToConnect) {
-                // If this profile is temporarily disconnected, don't try to change its priority
-                // until the temporary disconnect is released.
                 synchronized (this) {
                     ConnectionParams params = new ConnectionParams(profile, device);
-                    if (mTemporaryDisconnects.keySet().contains(params)) {
+                    // If this profile is inhibited, don't try to change its priority until the
+                    // inhibit is released. Instead, if the profile is being enabled, take it off of
+                    // the "previously disabled profiles" list, so it will be restored when all
+                    // inhibits are removed.
+                    if (mProfileInhibits.keySet().contains(params)) {
                         if (DBG) {
                             Log.i(TAG, "Not setting profile " + profile + " priority of "
-                                    + device.getAddress() + " to " + priority + ": "
-                                    + "temporarily disconnected");
+                                    + device.getAddress() + " to " + priority + ": inhibited");
+                        }
+                        if (priority == BluetoothProfile.PRIORITY_ON) {
+                            mAlreadyDisabledProfiles.remove(params);
                         }
                         continue;
                     }
@@ -578,10 +585,10 @@
             mCarBluetoothUserService = setupBluetoothUserService();
             // re-initialize for current user.
             initializeUserSpecificInfo();
-            // Restore temporary disconnects, if any, that were saved from last run...
-            restoreTemporaryDisconnectsFromSettings();
+            // Restore profile inhibits, if any, that were saved from last run...
+            restoreProfileInhibitsFromSettings();
             // ... and start trying to remove them.
-            removeRestoredTemporaryDisconnects();
+            removeRestoredProfileInhibits();
         }
 
         @Override
@@ -590,13 +597,13 @@
                 Log.d(TAG, "Before Unbinding from UserService");
             }
 
-            // Try to release temporary disconnects now, before CarBluetoothUserService goes away.
-            // This also stops any active attempts to remove restored disconnects.
+            // Try to release profile inhibits now, before CarBluetoothUserService goes away.
+            // This also stops any active attempts to remove restored inhibits.
             //
             // If any can't be released, they'll persist in settings and will be cleaned up
             // next time this user starts. This can happen if the Bluetooth profile proxies in
             // CarBluetoothUserService unbind before we get the chance to make calls on them.
-            releaseAllDisconnectRecordsBeforeUnbind();
+            releaseAllInhibitsBeforeUnbind();
 
             try {
                 if (mCarBluetoothUserService != null) {
@@ -974,34 +981,34 @@
     /**
      * Request to disconnect the given profile on the given device, and prevent it from reconnecting
      * until either the request is released, or the process owning the given token dies.
-     * @return True if the profile was successfully disconnected, false if an error occurred.
+     * @return True if the profile was successfully inhibited, false if an error occurred.
      */
-    public boolean requestProfileDisconnect(BluetoothDevice device, int profile, IBinder token) {
+    boolean requestProfileInhibit(BluetoothDevice device, int profile, IBinder token) {
         if (DBG) {
-            Log.d(TAG, "Request profile disconnect: profile " + Utils.getProfileName(profile)
+            Log.d(TAG, "Request profile inhibit: profile " + Utils.getProfileName(profile)
                     + ", device " + device.getAddress());
         }
         ConnectionParams params = new ConnectionParams(profile, device);
-        DisconnectRecord record = new DisconnectRecord(params, token);
-        return addDisconnectRecord(record);
+        InhibitRecord record = new InhibitRecord(params, token);
+        return addInhibitRecord(record);
     }
 
     /**
-     * Undo a previous call to {@link #requestProfileDisconnect} with the same parameters,
+     * Undo a previous call to {@link #requestProfileInhibit} with the same parameters,
      * and reconnect the profile if no other requests are active.
      *
      * @return True if the request was released, false if an error occurred.
      */
-    public boolean releaseProfileDisconnect(BluetoothDevice device, int profile, IBinder token) {
+    boolean releaseProfileInhibit(BluetoothDevice device, int profile, IBinder token) {
         if (DBG) {
-            Log.d(TAG, "Release profile disconnect: profile " + Utils.getProfileName(profile)
+            Log.d(TAG, "Release profile inhibit: profile " + Utils.getProfileName(profile)
                     + ", device " + device.getAddress());
         }
 
         ConnectionParams params = new ConnectionParams(profile, device);
-        DisconnectRecord record;
+        InhibitRecord record;
         synchronized (this) {
-            record = findDisconnectRecordLocked(params, token);
+            record = findInhibitRecordLocked(params, token);
         }
 
         if (record == null) {
@@ -1012,28 +1019,28 @@
         return record.removeSelf();
     }
 
-    /** Add a temporary disconnect record, disconnecting if necessary. */
-    private synchronized boolean addDisconnectRecord(DisconnectRecord record) {
+    /** Add a profile inhibit record, disabling the profile if necessary. */
+    private synchronized boolean addInhibitRecord(InhibitRecord record) {
         ConnectionParams params = record.getParams();
         if (!isProxyAvailable(params.getBluetoothProfile())) {
             return false;
         }
 
-        Set<DisconnectRecord> previousRecords = mTemporaryDisconnects.get(params);
-        if (findDisconnectRecordLocked(params, record.getToken()) != null) {
-            Log.e(TAG, "Disconnect request already registered - skipping duplicate");
+        Set<InhibitRecord> previousRecords = mProfileInhibits.get(params);
+        if (findInhibitRecordLocked(params, record.getToken()) != null) {
+            Log.e(TAG, "Inhibit request already registered - skipping duplicate");
             return false;
         }
 
         try {
             record.getToken().linkToDeath(record, 0);
         } catch (RemoteException e) {
-            Log.e(TAG, "Could not link to death on disconnect token (already dead?)", e);
+            Log.e(TAG, "Could not link to death on inhibit token (already dead?)", e);
             return false;
         }
 
         boolean isNewlyAdded = previousRecords.isEmpty();
-        mTemporaryDisconnects.put(params, record);
+        mProfileInhibits.put(params, record);
 
         if (isNewlyAdded) {
             try {
@@ -1042,8 +1049,8 @@
                                 params.getBluetoothProfile(),
                                 params.getBluetoothDevice());
                 if (priority == BluetoothProfile.PRIORITY_OFF) {
-                    // This profile was already disabled (and not as the result of a temporary
-                    // disconnect). Add it to the already-disabled list, and do nothing else.
+                    // This profile was already disabled (and not as the result of an inhibit).
+                    // Add it to the already-disabled list, and do nothing else.
                     mAlreadyDisabledProfiles.add(params);
 
                     if (DBG) {
@@ -1068,22 +1075,22 @@
             } catch (RemoteException e) {
                 Log.e(TAG, "Could not disable profile", e);
                 record.getToken().unlinkToDeath(record, 0);
-                mTemporaryDisconnects.remove(params, record);
+                mProfileInhibits.remove(params, record);
                 return false;
             }
         }
 
-        saveTemporaryDisconnectsToSettingsLocked();
+        saveProfileInhibitsToSettingsLocked();
         return true;
     }
 
-    /** Remove a given temporary disconnect record, reconnecting if necessary. */
-    private synchronized boolean removeDisconnectRecord(DisconnectRecord record) {
+    /** Remove a given profile inhibit record, reconnecting if necessary. */
+    private synchronized boolean removeInhibitRecord(InhibitRecord record) {
         ConnectionParams params = record.getParams();
         if (!isProxyAvailable(params.getBluetoothProfile())) {
             return false;
         }
-        if (!mTemporaryDisconnects.containsEntry(params, record)) {
+        if (!mProfileInhibits.containsEntry(params, record)) {
             Log.e(TAG, "Record already removed");
             // Removing something a second time vacuously succeeds.
             return true;
@@ -1092,23 +1099,23 @@
         // Re-enable profile before unlinking and removing the record, in case of error.
         // The profile should be re-enabled if this record is the only one left for that
         // device and profile combination.
-        if (mTemporaryDisconnects.get(params).size() == 1) {
+        if (mProfileInhibits.get(params).size() == 1) {
             if (!restoreProfilePriority(params)) {
                 return false;
             }
         }
 
         record.getToken().unlinkToDeath(record, 0);
-        mTemporaryDisconnects.remove(params, record);
+        mProfileInhibits.remove(params, record);
 
-        saveTemporaryDisconnectsToSettingsLocked();
+        saveProfileInhibitsToSettingsLocked();
         return true;
     }
 
-    /** Find the disconnect record, if any, corresponding to the given parameters and token. */
+    /** Find the inhibit record, if any, corresponding to the given parameters and token. */
     @Nullable
-    private DisconnectRecord findDisconnectRecordLocked(ConnectionParams params, IBinder token) {
-        return mTemporaryDisconnects.get(params)
+    private InhibitRecord findInhibitRecordLocked(ConnectionParams params, IBinder token) {
+        return mProfileInhibits.get(params)
             .stream()
             .filter(r -> r.getToken() == token)
             .findAny()
@@ -1123,7 +1130,7 @@
 
         if (mAlreadyDisabledProfiles.remove(params)) {
             // The profile does not need any state changes, since it was disabled
-            // before it was temporarily disconnected. Leave it disconnected.
+            // before it was inhibited. Leave it disabled.
             if (DBG) {
                 Log.d(TAG, "Not restoring profile "
                         + Utils.getProfileName(params.getBluetoothProfile()) + " for device "
@@ -1151,37 +1158,37 @@
         }
     }
 
-    /** Dump all currently-active temporary disconnects to {@link Settings.Secure}. */
-    private void saveTemporaryDisconnectsToSettingsLocked() {
-        Set<ConnectionParams> disconnectedProfiles = new HashSet<>(mTemporaryDisconnects.keySet());
-        // Don't write out profiles that were disconnected before a request was made, since
+    /** Dump all currently-active profile inhibits to {@link Settings.Secure}. */
+    private void saveProfileInhibitsToSettingsLocked() {
+        Set<ConnectionParams> inhibitedProfiles = new HashSet<>(mProfileInhibits.keySet());
+        // Don't write out profiles that were disabled before a request was made, since
         // restoring those profiles is a no-op.
-        disconnectedProfiles.removeAll(mAlreadyDisabledProfiles);
+        inhibitedProfiles.removeAll(mAlreadyDisabledProfiles);
         String savedDisconnects =
-                disconnectedProfiles
+                inhibitedProfiles
                         .stream()
                         .map(ConnectionParams::flattenToString)
                         .collect(Collectors.joining(SETTINGS_DELIMITER));
 
         if (DBG) {
-            Log.d(TAG, "Saving disconnects to settings for u" + mUserId + ": " + savedDisconnects);
+            Log.d(TAG, "Saving inhibits to settings for u" + mUserId + ": " + savedDisconnects);
         }
 
         Settings.Secure.putStringForUser(
-                mContext.getContentResolver(), KEY_BLUETOOTH_TEMPORARY_DISCONNECTS,
+                mContext.getContentResolver(), KEY_BLUETOOTH_PROFILES_INHIBITED,
                 savedDisconnects, mUserId);
     }
 
-    /** Create {@link DisconnectRecord}s for all temporary disconnects written to settings. */
-    private synchronized void restoreTemporaryDisconnectsFromSettings() {
+    /** Create {@link InhibitRecord}s for all profile inhibits written to settings. */
+    private synchronized void restoreProfileInhibitsFromSettings() {
         if (mBluetoothAdapter == null) {
-            Log.e(TAG, "Cannot restore disconnect records - Bluetooth not available");
+            Log.e(TAG, "Cannot restore inhibit records - Bluetooth not available");
             return;
         }
 
         String savedConnectionParams = Settings.Secure.getStringForUser(
                 mContext.getContentResolver(),
-                KEY_BLUETOOTH_TEMPORARY_DISCONNECTS,
+                KEY_BLUETOOTH_PROFILES_INHIBITED,
                 mUserId);
 
         if (TextUtils.isEmpty(savedConnectionParams)) {
@@ -1189,89 +1196,89 @@
         }
 
         if (DBG) {
-            Log.d(TAG, "Restoring temporary disconnects: " + savedConnectionParams);
+            Log.d(TAG, "Restoring profile inhibits: " + savedConnectionParams);
         }
 
         for (String paramsStr : savedConnectionParams.split(SETTINGS_DELIMITER)) {
             try {
                 ConnectionParams params = ConnectionParams.parse(paramsStr, mBluetoothAdapter);
-                DisconnectRecord record =
-                        new DisconnectRecord(params, RESTORED_TEMPORARY_DISCONNECT_TOKEN);
-                mTemporaryDisconnects.put(params, record);
-                mRestoredDisconnects.add(record);
+                InhibitRecord record =
+                        new InhibitRecord(params, RESTORED_PROFILE_INHIBIT_TOKEN);
+                mProfileInhibits.put(params, record);
+                mRestoredInhibits.add(record);
                 if (DBG) {
-                    Log.d(TAG, "Restored temporary disconnect for " + params);
+                    Log.d(TAG, "Restored profile inhibits for " + params);
                 }
             } catch (IllegalArgumentException e) {
-                Log.e(TAG, "Bad format for saved temporary disconnect: " + paramsStr, e);
+                Log.e(TAG, "Bad format for saved profile inhibit: " + paramsStr, e);
                 // We won't ever be able to fix a bad parse, so skip it and move on.
             }
         }
     }
 
     /**
-     * Try once to remove all temporary disconnects.
+     * Try once to remove all restored profile inhibits.
      *
      * If the CarBluetoothUserService is not yet available, or it hasn't yet bound its profile
      * proxies, the removal will fail, and will need to be retried later.
      */
-    private void tryRemoveRestoredTemporaryDisconnectsLocked() {
-        HashSet<DisconnectRecord> successfullyRemoved = new HashSet<>();
+    private void tryRemoveRestoredProfileInhibitsLocked() {
+        HashSet<InhibitRecord> successfullyRemoved = new HashSet<>();
 
-        for (DisconnectRecord record : mRestoredDisconnects) {
-            if (removeDisconnectRecord(record)) {
+        for (InhibitRecord record : mRestoredInhibits) {
+            if (removeInhibitRecord(record)) {
                 successfullyRemoved.add(record);
             }
         }
 
-        mRestoredDisconnects.removeAll(successfullyRemoved);
+        mRestoredInhibits.removeAll(successfullyRemoved);
     }
 
     /**
-     * Keep trying to remove all temporary disconnects that were restored from settings
-     * until all such temporary disconnects have been removed.
+     * Keep trying to remove all profile inhibits that were restored from settings
+     * until all such inhibits have been removed.
      */
-    private synchronized void removeRestoredTemporaryDisconnects() {
-        tryRemoveRestoredTemporaryDisconnectsLocked();
+    private synchronized void removeRestoredProfileInhibits() {
+        tryRemoveRestoredProfileInhibitsLocked();
 
-        if (!mRestoredDisconnects.isEmpty()) {
+        if (!mRestoredInhibits.isEmpty()) {
             if (DBG) {
-                Log.d(TAG, "Could not remove all restored temporary disconnects - "
+                Log.d(TAG, "Could not remove all restored profile inhibits - "
                         + "trying again in " + RESTORE_BACKOFF_MILLIS + "ms");
             }
             mHandler.postDelayed(
-                    this::removeRestoredTemporaryDisconnects,
-                    RESTORED_TEMPORARY_DISCONNECT_TOKEN,
+                    this::removeRestoredProfileInhibits,
+                    RESTORED_PROFILE_INHIBIT_TOKEN,
                     RESTORE_BACKOFF_MILLIS);
         }
     }
 
-    /** Release all active disconnect records prior to user switch or shutdown. */
-    private synchronized void releaseAllDisconnectRecordsBeforeUnbind() {
+    /** Release all active inhibit records prior to user switch or shutdown. */
+    private synchronized void releaseAllInhibitsBeforeUnbind() {
         if (DBG) {
-            Log.d(TAG, "Unbinding CarBluetoothUserService - releasing all temporary disconnects");
+            Log.d(TAG, "Unbinding CarBluetoothUserService - releasing all profile inhibits");
         }
-        for (ConnectionParams params : mTemporaryDisconnects.keySet()) {
-            for (DisconnectRecord record : mTemporaryDisconnects.get(params)) {
+        for (ConnectionParams params : mProfileInhibits.keySet()) {
+            for (InhibitRecord record : mProfileInhibits.get(params)) {
                 record.removeSelf();
             }
         }
 
-        // Some disconnects might be hanging around because they couldn't be cleaned up.
+        // Some inhibits might be hanging around because they couldn't be cleaned up.
         // Make sure they get persisted...
-        saveTemporaryDisconnectsToSettingsLocked();
+        saveProfileInhibitsToSettingsLocked();
         // ...then clear them from the map.
-        mTemporaryDisconnects.clear();
+        mProfileInhibits.clear();
 
-        // We don't need to maintain previously-disconnected profiles any more - they were already
-        // skipped in saveTemporaryDisconnectsToSettingsLocked() above, and they don't need any
+        // We don't need to maintain previously-disabled profiles any more - they were already
+        // skipped in saveProfileInhibitsToSettingsLocked() above, and they don't need any
         // further handling when the user resumes.
         mAlreadyDisabledProfiles.clear();
 
-        // Clean up bookkeeping for restored disconnects. (If any are still around, they'll be
+        // Clean up bookkeeping for restored inhibits. (If any are still around, they'll be
         // restored again when this user restarts.)
-        mHandler.removeCallbacksAndMessages(RESTORED_TEMPORARY_DISCONNECT_TOKEN);
-        mRestoredDisconnects.clear();
+        mHandler.removeCallbacksAndMessages(RESTORED_PROFILE_INHIBIT_TOKEN);
+        mRestoredInhibits.clear();
     }
 
     /**
@@ -2074,11 +2081,10 @@
         writer.println("*BluetoothDeviceConnectionPolicy*");
         printDeviceMap(writer);
         mBluetoothAutoConnectStateMachine.dump(writer);
-        writer.println("Temporary disconnects active:");
-        String disconnects;
+        String inhibits;
         synchronized (this) {
-            disconnects = mTemporaryDisconnects.keySet().toString();
+            inhibits = mProfileInhibits.keySet().toString();
         }
-        writer.println(disconnects);
+        writer.println("Inhibited profiles: " + inhibits);
     }
 }
diff --git a/service/src/com/android/car/CarBluetoothService.java b/service/src/com/android/car/CarBluetoothService.java
index a8ffdcc..d34d165 100644
--- a/service/src/com/android/car/CarBluetoothService.java
+++ b/service/src/com/android/car/CarBluetoothService.java
@@ -31,7 +31,6 @@
 import android.car.ICarBluetooth;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.os.Binder;
 import android.os.IBinder;
 import android.provider.Settings;
 import android.util.Log;
@@ -155,60 +154,28 @@
      * Request to disconnect the given profile on the given device, and prevent it from reconnecting
      * until either the request is released, or the process owning the given token dies.
      *
-     * @param device The device on which to disconnect a profile.
-     * @param profile The {@link android.bluetooth.BluetoothProfile} to disconnect.
-     * @param token A {@link IBinder} to be used as an identity for the request. If the process
-     *     owning the token dies, the request will automatically be released.
-     * @return True if the profile was successfully disconnected, false if an error occurred.
+     * @param device  The device on which to inhibit a profile.
+     * @param profile The {@link android.bluetooth.BluetoothProfile} to inhibit.
+     * @param token   A {@link IBinder} to be used as an identity for the request. If the process
+     *                owning the token dies, the request will automatically be released.
+     * @return True if the profile was successfully inhibited, false if an error occurred.
      */
-    @Override
-    public boolean requestTemporaryDisconnect(BluetoothDevice device, int profile, IBinder token) {
-        if (DBG) {
-            Log.d(TAG, "requestTemporaryDisconnect device=" + device + " profile=" + profile
-                    + " from uid " + Binder.getCallingUid());
-        }
-        try {
-            enforceBluetoothAdminPermission();
-            if (device == null) {
-                // Will be caught by AIDL and thrown to caller.
-                throw new NullPointerException("Null device in requestTemporaryDisconnect");
-            }
-            return mBluetoothDeviceConnectionPolicy
-                .requestProfileDisconnect(device, profile, token);
-        } catch (RuntimeException e) {
-            Log.e(TAG, "Error in requestTemporaryDisconnect", e);
-            throw e;
-        }
+    boolean requestProfileInhibit(BluetoothDevice device, int profile, IBinder token) {
+        return mBluetoothDeviceConnectionPolicy.requestProfileInhibit(device, profile, token);
     }
 
     /**
-     * Undo a previous call to {@link #requestProfileDisconnect} with the same parameters,
-     * and reconnect the profile if no other requests are active.
+     * Release an inhibit request made by {@link #requestProfileInhibit}, and reconnect the
+     * profile if no other inhibit requests are active.
      *
-     * @param device The device on which to release the disconnect request.
-     * @param profile The profile on which to release the disconnect request.
-     * @param token The token provided in the original call to {@link #requestTemporaryDisconnect}.
-     *
+     * @param device  The device on which to release the inhibit request.
+     * @param profile The profile on which to release the inhibit request.
+     * @param token   The token provided in the original call to
+     *                {@link #requestProfileInhibit}.
      * @return True if the request was released, false if an error occurred.
      */
-    @Override
-    public boolean releaseTemporaryDisconnect(BluetoothDevice device, int profile, IBinder token) {
-        if (DBG) {
-            Log.d(TAG, "releaseTemporaryDisconnect device=" + device + " profile=" + profile
-                    + " from uid " + Binder.getCallingUid());
-        }
-        try {
-            enforceBluetoothAdminPermission();
-            if (device == null) {
-                // Will be caught by AIDL and thrown to caller.
-                throw new NullPointerException("Null device in releaseTemporaryDisconnect");
-            }
-            return mBluetoothDeviceConnectionPolicy
-                .releaseProfileDisconnect(device, profile, token);
-        } catch (RuntimeException e) {
-            Log.e(TAG, "Error in releaseTemporaryDisconnect", e);
-            throw e;
-        }
+    boolean releaseProfileInhibit(BluetoothDevice device, int profile, IBinder token) {
+        return mBluetoothDeviceConnectionPolicy.releaseProfileInhibit(device, profile, token);
     }
 
     /**
diff --git a/service/src/com/android/car/CarProjectionService.java b/service/src/com/android/car/CarProjectionService.java
index 5bc8352..37fd9d0 100644
--- a/service/src/com/android/car/CarProjectionService.java
+++ b/service/src/com/android/car/CarProjectionService.java
@@ -28,6 +28,7 @@
 import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLING;
 
 import android.annotation.Nullable;
+import android.bluetooth.BluetoothDevice;
 import android.car.CarProjectionManager;
 import android.car.CarProjectionManager.ProjectionAccessPointCallback;
 import android.car.ICarProjection;
@@ -79,6 +80,7 @@
 
     private final ProjectionCallbackHolder mProjectionCallbacks;
     private final CarInputService mCarInputService;
+    private final CarBluetoothService mCarBluetoothService;
     private final Context mContext;
     private final WifiManager mWifiManager;
     private final Handler mHandler;
@@ -150,10 +152,12 @@
     private boolean mBound;
     private Intent mRegisteredService;
 
-    CarProjectionService(Context context, CarInputService carInputService) {
+    CarProjectionService(Context context, CarInputService carInputService,
+            CarBluetoothService carBluetoothService) {
         mContext = context;
         mHandler = new Handler();
         mCarInputService = carInputService;
+        mCarBluetoothService = carBluetoothService;
         mProjectionCallbacks = new ProjectionCallbackHolder(this);
         mWifiManager = context.getSystemService(WifiManager.class);
         mProjectionWifiConfiguration = createWifiConfiguration(context);
@@ -272,6 +276,70 @@
         }
     }
 
+    /**
+     * Request to disconnect the given profile on the given device, and prevent it from reconnecting
+     * until either the request is released, or the process owning the given token dies.
+     *
+     * @param device  The device on which to inhibit a profile.
+     * @param profile The {@link android.bluetooth.BluetoothProfile} to inhibit.
+     * @param token   A {@link IBinder} to be used as an identity for the request. If the process
+     *                owning the token dies, the request will automatically be released.
+     * @return True if the profile was successfully inhibited, false if an error occurred.
+     */
+    @Override
+    public boolean requestBluetoothProfileInhibit(
+            BluetoothDevice device, int profile, IBinder token) {
+        if (DBG) {
+            Log.d(TAG, "requestBluetoothProfileInhibit device=" + device + " profile=" + profile
+                    + " from uid " + Binder.getCallingUid());
+        }
+        try {
+            if (device == null) {
+                // Will be caught by AIDL and thrown to caller.
+                throw new NullPointerException("Device must not be null");
+            }
+            if (token == null) {
+                throw new NullPointerException("Token must not be null");
+            }
+            return mCarBluetoothService.requestProfileInhibit(device, profile, token);
+        } catch (RuntimeException e) {
+            Log.e(TAG, "Error in requestBluetoothProfileInhibit", e);
+            throw e;
+        }
+    }
+
+    /**
+     * Release an inhibit request made by {@link #requestBluetoothProfileInhibit}, and reconnect the
+     * profile if no other inhibit requests are active.
+     *
+     * @param device  The device on which to release the inhibit request.
+     * @param profile The profile on which to release the inhibit request.
+     * @param token   The token provided in the original call to
+     *                {@link #requestBluetoothProfileInhibit}.
+     * @return True if the request was released, false if an error occurred.
+     */
+    @Override
+    public boolean releaseBluetoothProfileInhibit(
+            BluetoothDevice device, int profile, IBinder token) {
+        if (DBG) {
+            Log.d(TAG, "releaseBluetoothProfileInhibit device=" + device + " profile=" + profile
+                    + " from uid " + Binder.getCallingUid());
+        }
+        try {
+            if (device == null) {
+                // Will be caught by AIDL and thrown to caller.
+                throw new NullPointerException("Device must not be null");
+            }
+            if (token == null) {
+                throw new NullPointerException("Token must not be null");
+            }
+            return mCarBluetoothService.releaseProfileInhibit(device, profile, token);
+        } catch (RuntimeException e) {
+            Log.e(TAG, "Error in releaseBluetoothProfileInhibit", e);
+            throw e;
+        }
+    }
+
     private void startAccessPoint() {
         synchronized (mLock) {
             switch (mWifiMode) {
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index adcedf3..63ea2a7 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -124,8 +124,12 @@
         mCarPackageManagerService = new CarPackageManagerService(serviceContext,
                 mCarUXRestrictionsService,
                 mSystemActivityMonitoringService);
+        mPerUserCarServiceHelper = new PerUserCarServiceHelper(serviceContext);
+        mCarBluetoothService = new CarBluetoothService(serviceContext, mCarPropertyService,
+                mPerUserCarServiceHelper, mCarUXRestrictionsService);
         mCarInputService = new CarInputService(serviceContext, mHal.getInputHal());
-        mCarProjectionService = new CarProjectionService(serviceContext, mCarInputService);
+        mCarProjectionService = new CarProjectionService(
+                serviceContext, mCarInputService, mCarBluetoothService);
         mGarageModeService = new GarageModeService(mContext);
         mAppFocusService = new AppFocusService(serviceContext, mSystemActivityMonitoringService);
         mCarAudioService = new CarAudioService(serviceContext);
@@ -134,9 +138,6 @@
                 mAppFocusService, mCarInputService);
         mSystemStateControllerService = new SystemStateControllerService(
                 serviceContext, mCarAudioService, this);
-        mPerUserCarServiceHelper = new PerUserCarServiceHelper(serviceContext);
-        mCarBluetoothService = new CarBluetoothService(serviceContext, mCarPropertyService,
-                mPerUserCarServiceHelper, mCarUXRestrictionsService);
         mVmsSubscriberService = new VmsSubscriberService(serviceContext, mHal.getVmsHal());
         mVmsPublisherService = new VmsPublisherService(serviceContext, mHal.getVmsHal());
         mCarDiagnosticService = new CarDiagnosticService(serviceContext, mHal.getDiagnosticHal());
@@ -162,11 +163,11 @@
         allServices.add(mCarAudioService);
         allServices.add(mCarNightService);
         allServices.add(mInstrumentClusterService);
-        allServices.add(mCarProjectionService);
         allServices.add(mSystemStateControllerService);
-        allServices.add(mCarBluetoothService);
-        allServices.add(mCarDiagnosticService);
         allServices.add(mPerUserCarServiceHelper);
+        allServices.add(mCarBluetoothService);
+        allServices.add(mCarProjectionService);
+        allServices.add(mCarDiagnosticService);
         allServices.add(mCarStorageMonitoringService);
         allServices.add(mCarConfigurationService);
         allServices.add(mVmsSubscriberService);