diff options
30 files changed, 644 insertions, 238 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 82c3383dc16a..eba74716b1a9 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -173,7 +173,6 @@ import java.net.InetAddress; import java.text.DateFormat; import java.util.ArrayList; import java.util.Arrays; -import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; @@ -223,9 +222,6 @@ public final class ActivityThread extends ClientTransactionHandler { // Whether to invoke an activity callback after delivering new configuration. private static final boolean REPORT_TO_ACTIVITY = true; - // Maximum number of recent tokens to maintain for debugging purposes - private static final int MAX_DESTROYED_ACTIVITIES = 10; - /** * Denotes an invalid sequence number corresponding to a process state change. */ @@ -258,8 +254,6 @@ public final class ActivityThread extends ClientTransactionHandler { final H mH = new H(); final Executor mExecutor = new HandlerExecutor(mH); final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>(); - final ArrayList<DestroyedActivityInfo> mRecentDestroyedActivities = new ArrayList<>(); - // List of new activities (via ActivityRecord.nextIdle) that should // be reported when next we idle. ActivityClientRecord mNewActivities = null; @@ -341,26 +335,6 @@ public final class ActivityThread extends ClientTransactionHandler { } } - /** - * TODO(b/71506345): Remove this once bug is resolved. - */ - private static final class DestroyedActivityInfo { - private final Integer mToken; - private final String mReason; - private final long mTime; - - DestroyedActivityInfo(Integer token, String reason) { - mToken = token; - mReason = reason; - mTime = System.currentTimeMillis(); - } - - void dump(PrintWriter pw, String prefix) { - pw.println(prefix + "[token:" + mToken + " | time:" + mTime + " | reason:" + mReason - + "]"); - } - } - // The lock of mProviderMap protects the following variables. final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap = new ArrayMap<ProviderKey, ProviderClientRecord>(); @@ -2195,32 +2169,6 @@ public final class ActivityThread extends ClientTransactionHandler { pw.println(String.format(format, objs)); } - @Override - public void dump(PrintWriter pw, String prefix) { - pw.println(prefix + "Activities:"); - - if (!mActivities.isEmpty()) { - final Iterator<Map.Entry<IBinder, ActivityClientRecord>> activitiesIterator = - mActivities.entrySet().iterator(); - - while (activitiesIterator.hasNext()) { - final ArrayMap.Entry<IBinder, ActivityClientRecord> entry = - activitiesIterator.next(); - pw.println(prefix + " [token:" + entry.getKey().hashCode() + " record:" - + entry.getValue().toString() + "]"); - } - } - - if (!mRecentDestroyedActivities.isEmpty()) { - pw.println(prefix + "Recent destroyed activities:"); - for (int i = 0, size = mRecentDestroyedActivities.size(); i < size; i++) { - final DestroyedActivityInfo info = mRecentDestroyedActivities.get(i); - pw.print(prefix); - info.dump(pw, " "); - } - } - } - public static void dumpMemInfoTable(PrintWriter pw, Debug.MemoryInfo memInfo, boolean checkin, boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly, int pid, String processName, @@ -4473,12 +4421,6 @@ public final class ActivityThread extends ClientTransactionHandler { r.setState(ON_DESTROY); } mActivities.remove(token); - mRecentDestroyedActivities.add(0, new DestroyedActivityInfo(token.hashCode(), reason)); - - final int recentDestroyedActivitiesSize = mRecentDestroyedActivities.size(); - if (recentDestroyedActivitiesSize > MAX_DESTROYED_ACTIVITIES) { - mRecentDestroyedActivities.remove(recentDestroyedActivitiesSize - 1); - } StrictMode.decrementExpectedActivityCount(activityClass); return r; } diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index 0639b00045c4..e26d989f09ef 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -27,7 +27,6 @@ import android.util.MergedConfiguration; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.ReferrerIntent; -import java.io.PrintWriter; import java.util.List; /** @@ -192,11 +191,4 @@ public abstract class ClientTransactionHandler { * Used to check if we should report relaunch to WM. * */ public abstract void reportRelaunch(IBinder token, PendingTransactionActions pendingActions); - - /** - * Debugging output. - * @param pw {@link PrintWriter} to write logs to. - * @param prefix Prefix to prepend to output. - */ - public abstract void dump(PrintWriter pw, String prefix); } diff --git a/core/java/android/app/servertransaction/ActivityLifecycleItem.java b/core/java/android/app/servertransaction/ActivityLifecycleItem.java index 7f8c50cd4ce5..c9193a9578e7 100644 --- a/core/java/android/app/servertransaction/ActivityLifecycleItem.java +++ b/core/java/android/app/servertransaction/ActivityLifecycleItem.java @@ -17,9 +17,7 @@ package android.app.servertransaction; import android.annotation.IntDef; -import android.os.Parcel; -import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -28,7 +26,6 @@ import java.lang.annotation.RetentionPolicy; * @hide */ public abstract class ActivityLifecycleItem extends ClientTransactionItem { - private String mDescription; @IntDef(prefix = { "UNDEFINED", "PRE_", "ON_" }, value = { UNDEFINED, @@ -57,43 +54,8 @@ public abstract class ActivityLifecycleItem extends ClientTransactionItem { @LifecycleState public abstract int getTargetState(); - - protected ActivityLifecycleItem() { - } - - protected ActivityLifecycleItem(Parcel in) { - mDescription = in.readString(); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - dest.writeString(mDescription); - } - - /** - * Sets a description that can be retrieved later for debugging purposes. - * @param description Description to set. - * @return The {@link ActivityLifecycleItem}. - */ - public ActivityLifecycleItem setDescription(String description) { - mDescription = description; - return this; - } - - /** - * Retrieves description if set through {@link #setDescription(String)}. - */ - public String getDescription() { - return mDescription; - } - - void dump(PrintWriter pw, String prefix) { - pw.println(prefix + "target state:" + getTargetState()); - pw.println(prefix + "description: " + mDescription); - } - + /** Called by subclasses to make sure base implementation is cleaned up */ @Override public void recycle() { - setDescription(null); } } diff --git a/core/java/android/app/servertransaction/ClientTransaction.java b/core/java/android/app/servertransaction/ClientTransaction.java index fc078798f6b9..08ad2f055774 100644 --- a/core/java/android/app/servertransaction/ClientTransaction.java +++ b/core/java/android/app/servertransaction/ClientTransaction.java @@ -26,7 +26,6 @@ import android.os.RemoteException; import com.android.internal.annotations.VisibleForTesting; -import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -238,12 +237,4 @@ public class ClientTransaction implements Parcelable, ObjectPoolItem { result = 31 * result + Objects.hashCode(mLifecycleStateRequest); return result; } - - void dump(PrintWriter pw, String prefix) { - pw.println(prefix + "mActivityToken:" + mActivityToken.hashCode()); - pw.println(prefix + "mLifecycleStateRequest:"); - if (mLifecycleStateRequest != null) { - mLifecycleStateRequest.dump(pw, prefix + " "); - } - } } diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java index 0edcf1884f01..b443166d151c 100644 --- a/core/java/android/app/servertransaction/DestroyActivityItem.java +++ b/core/java/android/app/servertransaction/DestroyActivityItem.java @@ -37,7 +37,7 @@ public class DestroyActivityItem extends ActivityLifecycleItem { PendingTransactionActions pendingActions) { Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy"); client.handleDestroyActivity(token, mFinished, mConfigChanges, - false /* getNonConfigInstance */, getDescription()); + false /* getNonConfigInstance */, "DestroyActivityItem"); Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER); } @@ -77,14 +77,12 @@ public class DestroyActivityItem extends ActivityLifecycleItem { /** Write to Parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); dest.writeBoolean(mFinished); dest.writeInt(mConfigChanges); } /** Read from Parcel. */ private DestroyActivityItem(Parcel in) { - super(in); mFinished = in.readBoolean(); mConfigChanges = in.readInt(); } diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java index 65e429126ac2..0c1eab56ec30 100644 --- a/core/java/android/app/servertransaction/PauseActivityItem.java +++ b/core/java/android/app/servertransaction/PauseActivityItem.java @@ -115,7 +115,6 @@ public class PauseActivityItem extends ActivityLifecycleItem { /** Write to Parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); dest.writeBoolean(mFinished); dest.writeBoolean(mUserLeaving); dest.writeInt(mConfigChanges); @@ -124,7 +123,6 @@ public class PauseActivityItem extends ActivityLifecycleItem { /** Read from Parcel. */ private PauseActivityItem(Parcel in) { - super(in); mFinished = in.readBoolean(); mUserLeaving = in.readBoolean(); mConfigChanges = in.readInt(); diff --git a/core/java/android/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java index d16bc97cbc87..909eec751d65 100644 --- a/core/java/android/app/servertransaction/ResumeActivityItem.java +++ b/core/java/android/app/servertransaction/ResumeActivityItem.java @@ -115,7 +115,6 @@ public class ResumeActivityItem extends ActivityLifecycleItem { /** Write to Parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); dest.writeInt(mProcState); dest.writeBoolean(mUpdateProcState); dest.writeBoolean(mIsForward); @@ -123,7 +122,6 @@ public class ResumeActivityItem extends ActivityLifecycleItem { /** Read from Parcel. */ private ResumeActivityItem(Parcel in) { - super(in); mProcState = in.readInt(); mUpdateProcState = in.readBoolean(); mIsForward = in.readBoolean(); diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java index 8db38d36c57e..87db2062a63d 100644 --- a/core/java/android/app/servertransaction/StopActivityItem.java +++ b/core/java/android/app/servertransaction/StopActivityItem.java @@ -85,14 +85,12 @@ public class StopActivityItem extends ActivityLifecycleItem { /** Write to Parcel. */ @Override public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); dest.writeBoolean(mShowWindow); dest.writeInt(mConfigChanges); } /** Read from Parcel. */ private StopActivityItem(Parcel in) { - super(in); mShowWindow = in.readBoolean(); mConfigChanges = in.readInt(); } diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java index 553c3ae15387..5c803a5c9599 100644 --- a/core/java/android/app/servertransaction/TransactionExecutor.java +++ b/core/java/android/app/servertransaction/TransactionExecutor.java @@ -34,8 +34,6 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; -import java.io.PrintWriter; -import java.io.StringWriter; import java.util.List; /** @@ -135,20 +133,7 @@ public class TransactionExecutor { final IBinder token = transaction.getActivityToken(); final ActivityClientRecord r = mTransactionHandler.getActivityClient(token); - // TODO(b/71506345): Remove once root cause is found. if (r == null) { - final StringWriter stringWriter = new StringWriter(); - final PrintWriter pw = new PrintWriter(stringWriter); - final String prefix = " "; - - pw.println("Lifecycle transaction does not have valid ActivityClientRecord."); - pw.println("Transaction:"); - transaction.dump(pw, prefix); - pw.println("Executor:"); - dump(pw, prefix); - - Slog.w(TAG, stringWriter.toString()); - // Ignore requests for non-existent client records for now. return; } @@ -224,9 +209,4 @@ public class TransactionExecutor { private static void log(String message) { if (DEBUG_RESOLVER) Slog.d(TAG, message); } - - private void dump(PrintWriter pw, String prefix) { - pw.println(prefix + "mTransactionHandler:"); - mTransactionHandler.dump(pw, prefix + " "); - } } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 9d3b53f232d8..c361a965c1e9 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -3355,7 +3355,7 @@ public abstract class PackageManager { @ComponentInfoFlags int flags) throws NameNotFoundException; /** - * Return a List of all packages that are installed on the device. + * Return a List of all packages that are installed for the current user. * * @param flags Additional option flags to modify the data returned. * @return A List of PackageInfo objects, one for each installed package, @@ -3742,8 +3742,8 @@ public abstract class PackageManager { throws NameNotFoundException; /** - * Return a List of all application packages that are installed on the - * device. If flag GET_UNINSTALLED_PACKAGES has been set, a list of all + * Return a List of all application packages that are installed for the + * current user. If flag GET_UNINSTALLED_PACKAGES has been set, a list of all * applications including those deleted with {@code DONT_DELETE_DATA} * (partially installed apps with data directory) will be returned. * diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index eecdb74f7d32..b13f831cb910 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -42,7 +42,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put("settings_about_phone_v2", "true"); DEFAULT_FLAGS.put("settings_bluetooth_while_driving", "false"); DEFAULT_FLAGS.put("settings_data_usage_v2", "true"); - DEFAULT_FLAGS.put("settings_audio_switcher", "false"); + DEFAULT_FLAGS.put("settings_audio_switcher", "true"); } /** diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 937e9f4ec5c5..730c3729e6d7 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1334,7 +1334,6 @@ public final class ViewRootImpl implements ViewParent, if (renderer != null) { renderer.destroyHardwareResources(mView); } - mSurface.release(); } for (int i = 0; i < mWindowStoppedCallbacks.size(); i++) { diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp index 07052cdab48b..9e7304678021 100644 --- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp +++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp @@ -53,6 +53,7 @@ TaskManager* SkiaPipeline::getTaskManager() { } void SkiaPipeline::onDestroyHardwareResources() { + unpinImages(); mRenderThread.cacheManager().trimStaleResources(); } diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp index 3ca92953e5f7..f510a2055309 100644 --- a/libs/hwui/renderthread/CacheManager.cpp +++ b/libs/hwui/renderthread/CacheManager.cpp @@ -166,10 +166,7 @@ void CacheManager::trimStaleResources() { return; } mGrContext->flush(); - // Here we purge all the unlocked scratch resources (leaving those resources w/ persistent data) - // and then purge those w/ persistent data based on age. - mGrContext->purgeUnlockedResources(true); - mGrContext->purgeResourcesNotUsedInMs(std::chrono::seconds(10)); + mGrContext->purgeResourcesNotUsedInMs(std::chrono::seconds(30)); } sp<skiapipeline::VectorDrawableAtlas> CacheManager::acquireVectorDrawableAtlas() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java index 06fe4de4b9b6..fb268abb8954 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothEventManager.java @@ -272,6 +272,14 @@ public class BluetoothEventManager { } } + void dispatchDeviceRemoved(CachedBluetoothDevice cachedDevice) { + synchronized (mCallbacks) { + for (BluetoothCallback callback : mCallbacks) { + callback.onDeviceDeleted(cachedDevice); + } + } + } + private class DeviceDisappearedHandler implements Handler { public void onReceive(Context context, Intent intent, BluetoothDevice device) { @@ -331,6 +339,10 @@ public class BluetoothEventManager { cachedDevice.onBondingStateChanged(bondState); if (bondState == BluetoothDevice.BOND_NONE) { + /* Check if we need to remove other Hearing Aid devices */ + if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) { + mDeviceManager.onDeviceUnpaired(cachedDevice); + } int reason = intent.getIntExtra(BluetoothDevice.EXTRA_REASON, BluetoothDevice.ERROR); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index dc2eceace066..62856e4e9082 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -18,6 +18,7 @@ package com.android.settingslib.bluetooth; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHearingAid; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; import android.content.Context; @@ -53,6 +54,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> private final BluetoothDevice mDevice; //TODO: consider remove, BluetoothDevice.getName() is already cached private String mName; + private long mHiSyncId; // Need this since there is no method for getting RSSI private short mRssi; //TODO: consider remove, BluetoothDevice.getBluetoothClass() is already cached @@ -94,6 +96,17 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> */ private boolean mIsConnectingErrorPossible; + public long getHiSyncId() { + return mHiSyncId; + } + + public void setHiSyncId(long id) { + if (Utils.D) { + Log.d(TAG, "setHiSyncId: mDevice " + mDevice + ", id " + id); + } + mHiSyncId = id; + } + /** * Last time a bt profile auto-connect was attempted. * If an ACTION_UUID intent comes in within @@ -175,6 +188,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> mDevice = device; mProfileConnectionState = new HashMap<LocalBluetoothProfile, Integer>(); fillData(); + mHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID; } public void disconnect() { @@ -336,7 +350,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> } } else if (Utils.V) { Log.v(TAG, "Framework rejected command immediately:REMOVE_BOND " + - describe(null)); + describe(null)); } } } @@ -1065,4 +1079,20 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> return getBondState() == BluetoothDevice.BOND_BONDING ? mContext.getString(R.string.bluetooth_pairing) : null; } + + /** + * @return {@code true} if {@code cachedBluetoothDevice} is a2dp device + */ + public boolean isA2dpDevice() { + return mProfileManager.getA2dpProfile().getConnectionStatus(mDevice) == + BluetoothProfile.STATE_CONNECTED; + } + + /** + * @return {@code true} if {@code cachedBluetoothDevice} is HFP device + */ + public boolean isHfpDevice() { + return mProfileManager.getHeadsetProfile().getConnectionStatus(mDevice) == + BluetoothProfile.STATE_CONNECTED; + } } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java index a8e0039ce3a4..50c6aac57e8f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java @@ -18,12 +18,17 @@ package com.android.settingslib.bluetooth; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothHearingAid; import android.content.Context; import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; + import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; /** @@ -34,10 +39,20 @@ public class CachedBluetoothDeviceManager { private static final boolean DEBUG = Utils.D; private Context mContext; - private final List<CachedBluetoothDevice> mCachedDevices = - new ArrayList<CachedBluetoothDevice>(); private final LocalBluetoothManager mBtManager; + @VisibleForTesting + final List<CachedBluetoothDevice> mCachedDevices = + new ArrayList<CachedBluetoothDevice>(); + // Contains the list of hearing aid devices that should not be shown in the UI. + @VisibleForTesting + final List<CachedBluetoothDevice> mHearingAidDevicesNotAddedInCache + = new ArrayList<CachedBluetoothDevice>(); + // Maintains a list of devices which are added in mCachedDevices and have hiSyncIds. + @VisibleForTesting + final Map<Long, CachedBluetoothDevice> mCachedDevicesMapForHearingAids + = new HashMap<Long, CachedBluetoothDevice>(); + CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) { mContext = context; mBtManager = localBtManager; @@ -69,12 +84,17 @@ public class CachedBluetoothDeviceManager { * @return the cached device object for this device, or null if it has * not been previously seen */ - public CachedBluetoothDevice findDevice(BluetoothDevice device) { + public synchronized CachedBluetoothDevice findDevice(BluetoothDevice device) { for (CachedBluetoothDevice cachedDevice : mCachedDevices) { if (cachedDevice.getDevice().equals(device)) { return cachedDevice; } } + for (CachedBluetoothDevice notCachedDevice : mHearingAidDevicesNotAddedInCache) { + if (notCachedDevice.getDevice().equals(device)) { + return notCachedDevice; + } + } return null; } @@ -89,14 +109,103 @@ public class CachedBluetoothDeviceManager { BluetoothDevice device) { CachedBluetoothDevice newDevice = new CachedBluetoothDevice(mContext, adapter, profileManager, device); - synchronized (mCachedDevices) { - mCachedDevices.add(newDevice); - mBtManager.getEventManager().dispatchDeviceAdded(newDevice); + if (profileManager.getHearingAidProfile() != null + && profileManager.getHearingAidProfile().getHiSyncId(newDevice.getDevice()) + != BluetoothHearingAid.HI_SYNC_ID_INVALID) { + newDevice.setHiSyncId(profileManager.getHearingAidProfile() + .getHiSyncId(newDevice.getDevice())); } + // Just add one of the hearing aids from a pair in the list that is shown in the UI. + if (isPairAddedInCache(newDevice.getHiSyncId())) { + synchronized (this) { + mHearingAidDevicesNotAddedInCache.add(newDevice); + } + } else { + synchronized (this) { + mCachedDevices.add(newDevice); + if (newDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID + && !mCachedDevicesMapForHearingAids.containsKey(newDevice.getHiSyncId())) { + mCachedDevicesMapForHearingAids.put(newDevice.getHiSyncId(), newDevice); + } + mBtManager.getEventManager().dispatchDeviceAdded(newDevice); + } + } + return newDevice; } /** + * Returns true if the one of the two hearing aid devices is already cached for UI. + * + * @param long hiSyncId + * @return {@code True} if one of the two hearing aid devices is is already cached for UI. + */ + private synchronized boolean isPairAddedInCache(long hiSyncId) { + if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID) { + return false; + } + if(mCachedDevicesMapForHearingAids.containsKey(hiSyncId)) { + return true; + } + return false; + } + + /** + * Returns device summary of the pair of the hearing aid passed as the parameter. + * + * @param CachedBluetoothDevice device + * @return Device summary, or if the pair does not exist or if its not a hearing aid, + * then {@code null}. + */ + public synchronized String getHearingAidPairDeviceSummary(CachedBluetoothDevice device) { + String pairDeviceSummary = null; + if (device.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) { + for (CachedBluetoothDevice hearingAidDevice : mHearingAidDevicesNotAddedInCache) { + if (hearingAidDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID + && hearingAidDevice.getHiSyncId() == device.getHiSyncId()) { + pairDeviceSummary = hearingAidDevice.getConnectionSummary(); + } + } + } + return pairDeviceSummary; + } + + /** + * Adds the 2nd hearing aid in a pair in a list that maintains the hearing aids that are + * not dispalyed in the UI. + * + * @param CachedBluetoothDevice device + */ + public synchronized void addDeviceNotaddedInMap(CachedBluetoothDevice device) { + mHearingAidDevicesNotAddedInCache.add(device); + } + + /** + * Updates the Hearing Aid devices; specifically the HiSyncId's. This routine is called when the + * Hearing Aid Service is connected and the HiSyncId's are now available. + * @param LocalBluetoothProfileManager profileManager + */ + public synchronized void updateHearingAidsDevices(LocalBluetoothProfileManager profileManager) { + HearingAidProfile profileProxy = profileManager.getHearingAidProfile(); + if (profileProxy == null) { + log("updateHearingAidsDevices: getHearingAidProfile() is null"); + return; + } + for (CachedBluetoothDevice cachedDevice : mCachedDevices) { + if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID) { + continue; + } + + long newHiSyncId = profileProxy.getHiSyncId(cachedDevice.getDevice()); + + if (newHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID) { + cachedDevice.setHiSyncId(newHiSyncId); + onHiSyncIdChanged(newHiSyncId); + } + } + } + + /** * Attempts to get the name of a remote device, otherwise returns the address. * * @param device The remote device. @@ -117,23 +226,29 @@ public class CachedBluetoothDeviceManager { } public synchronized void clearNonBondedDevices() { - for (int i = mCachedDevices.size() - 1; i >= 0; i--) { - CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); - if (cachedDevice.getBondState() == BluetoothDevice.BOND_NONE) { - mCachedDevices.remove(i); - } - } + + mCachedDevicesMapForHearingAids.entrySet().removeIf(entries + -> entries.getValue().getBondState() == BluetoothDevice.BOND_NONE); + + mCachedDevices.removeIf(cachedDevice + -> cachedDevice.getBondState() == BluetoothDevice.BOND_NONE); + + mHearingAidDevicesNotAddedInCache.removeIf(hearingAidDevice + -> hearingAidDevice.getBondState() == BluetoothDevice.BOND_NONE); } public synchronized void onScanningStateChanged(boolean started) { if (!started) return; - // If starting a new scan, clear old visibility // Iterate in reverse order since devices may be removed. for (int i = mCachedDevices.size() - 1; i >= 0; i--) { CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); cachedDevice.setJustDiscovered(false); } + for (int i = mHearingAidDevicesNotAddedInCache.size() - 1; i >= 0; i--) { + CachedBluetoothDevice notCachedDevice = mHearingAidDevicesNotAddedInCache.get(i); + notCachedDevice.setJustDiscovered(false); + } } public synchronized void onBtClassChanged(BluetoothDevice device) { @@ -159,6 +274,11 @@ public class CachedBluetoothDeviceManager { if (cachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) { cachedDevice.setJustDiscovered(false); mCachedDevices.remove(i); + if (cachedDevice.getHiSyncId() != BluetoothHearingAid.HI_SYNC_ID_INVALID + && mCachedDevicesMapForHearingAids.containsKey(cachedDevice.getHiSyncId())) + { + mCachedDevicesMapForHearingAids.remove(cachedDevice.getHiSyncId()); + } } else { // For bonded devices, we need to clear the connection status so that // when BT is enabled next time, device connection status shall be retrieved @@ -166,6 +286,18 @@ public class CachedBluetoothDeviceManager { cachedDevice.clearProfileConnectionState(); } } + for (int i = mHearingAidDevicesNotAddedInCache.size() - 1; i >= 0; i--) { + CachedBluetoothDevice notCachedDevice = mHearingAidDevicesNotAddedInCache.get(i); + if (notCachedDevice.getBondState() != BluetoothDevice.BOND_BONDED) { + notCachedDevice.setJustDiscovered(false); + mHearingAidDevicesNotAddedInCache.remove(i); + } else { + // For bonded devices, we need to clear the connection status so that + // when BT is enabled next time, device connection status shall be retrieved + // by making a binder call. + notCachedDevice.clearProfileConnectionState(); + } + } } } @@ -177,6 +309,49 @@ public class CachedBluetoothDeviceManager { } } + public synchronized void onHiSyncIdChanged(long hiSyncId) { + int firstMatchedIndex = -1; + + for (int i = mCachedDevices.size() - 1; i >= 0; i--) { + CachedBluetoothDevice cachedDevice = mCachedDevices.get(i); + if (cachedDevice.getHiSyncId() == hiSyncId) { + if (firstMatchedIndex != -1) { + /* Found the second one */ + mCachedDevices.remove(i); + mHearingAidDevicesNotAddedInCache.add(cachedDevice); + // Since the hiSyncIds have been updated for a connected pair of hearing aids, + // we remove the entry of one the hearing aids from the UI. Unless the + // hiSyncId get updated, the system does not know its a hearing aid, so we add + // both the hearing aids as separate entries in the UI first, then remove one + // of them after the hiSyncId is populated. + log("onHiSyncIdChanged: removed device=" + cachedDevice + ", with hiSyncId=" + + hiSyncId); + mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice); + break; + } else { + mCachedDevicesMapForHearingAids.put(hiSyncId, cachedDevice); + firstMatchedIndex = i; + } + } + } + } + + public synchronized void onDeviceUnpaired(CachedBluetoothDevice device) { + final long hiSyncId = device.getHiSyncId(); + + if (hiSyncId == BluetoothHearingAid.HI_SYNC_ID_INVALID) return; + + for (int i = mHearingAidDevicesNotAddedInCache.size() - 1; i >= 0; i--) { + CachedBluetoothDevice cachedDevice = mHearingAidDevicesNotAddedInCache.get(i); + if (cachedDevice.getHiSyncId() == hiSyncId) { + // TODO: Look for more cleanups on unpairing the device. + mHearingAidDevicesNotAddedInCache.remove(i); + if (device == cachedDevice) continue; + cachedDevice.unpair(); + } + } + } + private void log(String msg) { if (DEBUG) { Log.d(TAG, msg); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java index f9f80eb0d3c4..ad813368fbd0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java @@ -168,6 +168,11 @@ public class HeadsetProfile implements LocalBluetoothProfile { return mService.isAudioOn(); } + public int getAudioState(BluetoothDevice device) { + if (mService == null) return BluetoothHeadset.STATE_AUDIO_DISCONNECTED; + return mService.getAudioState(device); + } + public boolean isPreferred(BluetoothDevice device) { if (mService == null) return false; return mService.getPriority(device) > BluetoothProfile.PRIORITY_OFF; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java index 6c5ecbf2db40..da2ae7f8a320 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java @@ -67,12 +67,19 @@ public class HearingAidProfile implements LocalBluetoothProfile { CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice); // we may add a new device here, but generally this should not happen if (device == null) { - Log.w(TAG, "HearingAidProfile found new device: " + nextDevice); + if (V) { + Log.d(TAG, "HearingAidProfile found new device: " + nextDevice); + } device = mDeviceManager.addDevice(mLocalAdapter, mProfileManager, nextDevice); } - device.onProfileStateChanged(HearingAidProfile.this, BluetoothProfile.STATE_CONNECTED); + device.onProfileStateChanged(HearingAidProfile.this, + BluetoothProfile.STATE_CONNECTED); device.refresh(); } + + // Check current list of CachedDevices to see if any are Hearing Aid devices. + mDeviceManager.updateHearingAidsDevices(mProfileManager); + mIsProfileReady=true; } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java index 1e0cce98c791..11854b1ce86f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java @@ -354,6 +354,22 @@ public class LocalBluetoothProfileManager { oldState == BluetoothProfile.STATE_CONNECTING) { Log.i(TAG, "Failed to connect " + mProfile + " device"); } + + if (getHearingAidProfile() != null && + mProfile instanceof HearingAidProfile && + (newState == BluetoothProfile.STATE_CONNECTED)) { + // Check if the HiSyncID has being initialized + if (cachedDevice.getHiSyncId() == BluetoothHearingAid.HI_SYNC_ID_INVALID) { + + long newHiSyncId = getHearingAidProfile().getHiSyncId(cachedDevice.getDevice()); + + if (newHiSyncId != BluetoothHearingAid.HI_SYNC_ID_INVALID) { + cachedDevice.setHiSyncId(newHiSyncId); + mDeviceManager.onHiSyncIdChanged(newHiSyncId); + } + } + } + cachedDevice.onProfileStateChanged(mProfile, newState); cachedDevice.refresh(); } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java index 2f5eead38993..c3bd16195140 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java @@ -48,10 +48,18 @@ import java.util.Collection; public class CachedBluetoothDeviceManagerTest { private final static String DEVICE_NAME_1 = "TestName_1"; private final static String DEVICE_NAME_2 = "TestName_2"; + private final static String DEVICE_NAME_3 = "TestName_3"; private final static String DEVICE_ALIAS_1 = "TestAlias_1"; private final static String DEVICE_ALIAS_2 = "TestAlias_2"; + private final static String DEVICE_ALIAS_3 = "TestAlias_3"; private final static String DEVICE_ADDRESS_1 = "AA:BB:CC:DD:EE:11"; private final static String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22"; + private final static String DEVICE_ADDRESS_3 = "AA:BB:CC:DD:EE:33"; + private final static String DEVICE_SUMMARY_1 = "summary 1"; + private final static String DEVICE_SUMMARY_2 = "summary 2"; + private final static String DEVICE_SUMMARY_3 = "summary 3"; + private final static long HISYNCID1 = 10; + private final static long HISYNCID2 = 11; private final BluetoothClass DEVICE_CLASS_1 = new BluetoothClass(BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES); private final BluetoothClass DEVICE_CLASS_2 = @@ -76,6 +84,11 @@ public class CachedBluetoothDeviceManagerTest { private BluetoothDevice mDevice1; @Mock private BluetoothDevice mDevice2; + @Mock + private BluetoothDevice mDevice3; + private CachedBluetoothDevice mCachedDevice1; + private CachedBluetoothDevice mCachedDevice2; + private CachedBluetoothDevice mCachedDevice3; private CachedBluetoothDeviceManager mCachedDeviceManager; private Context mContext; private String[] mActiveDeviceStringsArray; @@ -90,12 +103,16 @@ public class CachedBluetoothDeviceManagerTest { mContext = RuntimeEnvironment.application; when(mDevice1.getAddress()).thenReturn(DEVICE_ADDRESS_1); when(mDevice2.getAddress()).thenReturn(DEVICE_ADDRESS_2); + when(mDevice3.getAddress()).thenReturn(DEVICE_ADDRESS_3); when(mDevice1.getName()).thenReturn(DEVICE_NAME_1); when(mDevice2.getName()).thenReturn(DEVICE_NAME_2); + when(mDevice3.getName()).thenReturn(DEVICE_NAME_3); when(mDevice1.getAliasName()).thenReturn(DEVICE_ALIAS_1); when(mDevice2.getAliasName()).thenReturn(DEVICE_ALIAS_2); + when(mDevice3.getAliasName()).thenReturn(DEVICE_ALIAS_3); when(mDevice1.getBluetoothClass()).thenReturn(DEVICE_CLASS_1); when(mDevice2.getBluetoothClass()).thenReturn(DEVICE_CLASS_2); + when(mDevice3.getBluetoothClass()).thenReturn(DEVICE_CLASS_2); when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager); when(mLocalAdapter.getBluetoothState()).thenReturn(BluetoothAdapter.STATE_ON); @@ -104,6 +121,12 @@ public class CachedBluetoothDeviceManagerTest { when(mPanProfile.isProfileReady()).thenReturn(true); when(mHearingAidProfile.isProfileReady()).thenReturn(true); mCachedDeviceManager = new CachedBluetoothDeviceManager(mContext, mLocalBluetoothManager); + mCachedDevice1 = spy( + new CachedBluetoothDevice(mContext, mLocalAdapter, mLocalProfileManager, mDevice1)); + mCachedDevice2 = spy( + new CachedBluetoothDevice(mContext, mLocalAdapter, mLocalProfileManager, mDevice2)); + mCachedDevice3 = spy( + new CachedBluetoothDevice(mContext, mLocalAdapter, mLocalProfileManager, mDevice3)); } /** @@ -188,6 +211,289 @@ public class CachedBluetoothDeviceManagerTest { } /** + * Test to verify clearNonBondedDevices() for hearing aids. + */ + @Test + public void testClearNonBondedDevices_HearingAids_nonBondedHAsClearedFromCachedDevicesMap() { + when(mDevice1.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); + when(mDevice2.getBondState()).thenReturn(BluetoothDevice.BOND_NONE); + + mCachedDevice1.setHiSyncId(HISYNCID1); + mCachedDevice2.setHiSyncId(HISYNCID2); + mCachedDeviceManager.mCachedDevicesMapForHearingAids.put(HISYNCID1, mCachedDevice1); + mCachedDeviceManager.mCachedDevicesMapForHearingAids.put(HISYNCID2, mCachedDevice2); + + mCachedDeviceManager.clearNonBondedDevices(); + + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values()) + .doesNotContain(mCachedDevice2); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values()) + .contains(mCachedDevice1); + } + + /** + * Test to verify onHiSyncIdChanged() for hearing aid devices with same HiSyncId. + */ + @Test + public void testOnDeviceAdded_sameHiSyncId_populateInDifferentLists() { + CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter, + mLocalProfileManager, mDevice1); + assertThat(cachedDevice1).isNotNull(); + CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mLocalAdapter, + mLocalProfileManager, mDevice2); + assertThat(cachedDevice2).isNotNull(); + + // Since both devices do not have hiSyncId, they should be added in mCachedDevices. + assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(2); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).isEmpty(); + + cachedDevice1.setHiSyncId(HISYNCID1); + cachedDevice2.setHiSyncId(HISYNCID1); + mCachedDeviceManager.onHiSyncIdChanged(HISYNCID1); + + // Since both devices have the same hiSyncId, one should remain in mCachedDevices + // and the other should be removed from mCachedDevices and get added in + // mHearingAidDevicesNotAddedInCache. The one that is in mCachedDevices should also be + // added in mCachedDevicesMapForHearingAids. + assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(1); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).hasSize(1); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(1); + Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy(); + assertThat(devices).contains(cachedDevice2); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values()) + .contains(cachedDevice2); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).contains(cachedDevice1); + } + + /** + * Test to verify onHiSyncIdChanged() for hearing aid devices with different HiSyncId. + */ + @Test + public void testOnDeviceAdded_differentHiSyncId_populateInSameList() { + CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter, + mLocalProfileManager, mDevice1); + assertThat(cachedDevice1).isNotNull(); + CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mLocalAdapter, + mLocalProfileManager, mDevice2); + assertThat(cachedDevice2).isNotNull(); + + // Since both devices do not have hiSyncId, they should be added in mCachedDevices. + assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(2); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).isEmpty(); + + cachedDevice1.setHiSyncId(HISYNCID1); + cachedDevice2.setHiSyncId(HISYNCID2); + mCachedDeviceManager.onHiSyncIdChanged(HISYNCID1); + mCachedDeviceManager.onHiSyncIdChanged(HISYNCID2); + + // Since both devices do not have same hiSyncId, they should remain in mCachedDevices and + // also be added in mCachedDevicesMapForHearingAids. + assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(2); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(2); + Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy(); + assertThat(devices).contains(cachedDevice2); + assertThat(devices).contains(cachedDevice1); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values()) + .contains(cachedDevice1); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values()) + .contains(cachedDevice2); + } + + /** + * Test to verify OnDeviceUnpaired() for a paired hearing Aid device pair. + */ + @Test + public void testOnDeviceUnpaired_bothHearingAidsPaired_removesItsPairFromList() { + CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter, + mLocalProfileManager, mDevice1); + assertThat(cachedDevice1).isNotNull(); + CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mLocalAdapter, + mLocalProfileManager, mDevice2); + assertThat(cachedDevice2).isNotNull(); + + cachedDevice1.setHiSyncId(HISYNCID1); + cachedDevice2.setHiSyncId(HISYNCID1); + mCachedDeviceManager.onHiSyncIdChanged(HISYNCID1); + + // Check if one device is in mCachedDevices and one in mHearingAidDevicesNotAddedInCache. + Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy(); + assertThat(devices).contains(cachedDevice2); + assertThat(devices).doesNotContain(cachedDevice1); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).contains(cachedDevice1); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache) + .doesNotContain(cachedDevice2); + + // Call onDeviceUnpaired for the one in mCachedDevices. + mCachedDeviceManager.onDeviceUnpaired(cachedDevice2); + + // Check if its pair is removed from mHearingAidDevicesNotAddedInCache. + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache) + .doesNotContain(cachedDevice1); + } + + /** + * Test to verify OnDeviceUnpaired() for paired hearing Aid devices which are not a pair. + */ + @Test + public void testOnDeviceUnpaired_bothHearingAidsNotPaired_doesNotRemoveAnyDeviceFromList() { + CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter, + mLocalProfileManager, mDevice1); + assertThat(cachedDevice1).isNotNull(); + CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mLocalAdapter, + mLocalProfileManager, mDevice2); + assertThat(cachedDevice2).isNotNull(); + CachedBluetoothDevice cachedDevice3 = mCachedDeviceManager.addDevice(mLocalAdapter, + mLocalProfileManager, mDevice3); + assertThat(cachedDevice2).isNotNull(); + + cachedDevice1.setHiSyncId(HISYNCID1); + cachedDevice2.setHiSyncId(HISYNCID1); + cachedDevice3.setHiSyncId(HISYNCID2); + mCachedDeviceManager.onHiSyncIdChanged(HISYNCID1); + mCachedDeviceManager.onHiSyncIdChanged(HISYNCID2); + + // Check if one device is in mCachedDevices and one in mHearingAidDevicesNotAddedInCache. + Collection<CachedBluetoothDevice> devices = mCachedDeviceManager.getCachedDevicesCopy(); + assertThat(devices).contains(cachedDevice2); + assertThat(devices).contains(cachedDevice3); + assertThat(devices).doesNotContain(cachedDevice1); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).contains(cachedDevice1); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache) + .doesNotContain(cachedDevice2); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache) + .doesNotContain(cachedDevice3); + + // Call onDeviceUnpaired for the one in mCachedDevices with no pair. + mCachedDeviceManager.onDeviceUnpaired(cachedDevice3); + + // Check if no list is changed. + devices = mCachedDeviceManager.getCachedDevicesCopy(); + assertThat(devices).contains(cachedDevice2); + assertThat(devices).contains(cachedDevice3); + assertThat(devices).doesNotContain(cachedDevice1); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).contains(cachedDevice1); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache) + .doesNotContain(cachedDevice2); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache) + .doesNotContain(cachedDevice3); + } + + /** + * Test to verify addDevice() for hearing aid devices with same HiSyncId. + */ + @Test + public void testAddDevice_hearingAidDevicesWithSameHiSyncId_populateInDifferentLists() { + doAnswer((invocation) -> mHearingAidProfile).when(mLocalProfileManager) + .getHearingAidProfile(); + doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice1); + doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice2); + + CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter, + mLocalProfileManager, mDevice1); + assertThat(cachedDevice1).isNotNull(); + // The first hearing aid device should be populated in mCachedDevice and + // mCachedDevicesMapForHearingAids. + assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(1); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(1); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values()) + .contains(cachedDevice1); + + CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mLocalAdapter, + mLocalProfileManager, mDevice2); + assertThat(cachedDevice2).isNotNull(); + // The second hearing aid device should be populated in mHearingAidDevicesNotAddedInCache. + assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(1); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).hasSize(1); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(1); + } + + /** + * Test to verify addDevice() for hearing aid devices with different HiSyncId. + */ + @Test + public void testAddDevice_hearingAidDevicesWithDifferentHiSyncId_populateInSameList() { + doAnswer((invocation) -> mHearingAidProfile).when(mLocalProfileManager) + .getHearingAidProfile(); + doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice1); + doAnswer((invocation) -> HISYNCID2).when(mHearingAidProfile).getHiSyncId(mDevice2); + CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mLocalAdapter, + mLocalProfileManager, mDevice1); + assertThat(cachedDevice1).isNotNull(); + // The first hearing aid device should be populated in mCachedDevice and + // mCachedDevicesMapForHearingAids. + assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(1); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(1); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values()) + .contains(cachedDevice1); + + CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mLocalAdapter, + mLocalProfileManager, mDevice2); + assertThat(cachedDevice2).isNotNull(); + // The second hearing aid device should also be populated in mCachedDevice + // and mCachedDevicesMapForHearingAids as its not a pair of the first one. + assertThat(mCachedDeviceManager.getCachedDevicesCopy()).hasSize(2); + assertThat(mCachedDeviceManager.mHearingAidDevicesNotAddedInCache).isEmpty(); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids).hasSize(2); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values()) + .contains(cachedDevice1); + assertThat(mCachedDeviceManager.mCachedDevicesMapForHearingAids.values()) + .contains(cachedDevice2); + } + + /** + * Test to verify getHearingAidPairDeviceSummary() for hearing aid devices with same HiSyncId. + */ + @Test + public void testGetHearingAidPairDeviceSummary_bothHearingAidsPaired_returnsSummaryOfPair() { + mCachedDevice1.setHiSyncId(HISYNCID1); + mCachedDevice2.setHiSyncId(HISYNCID1); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mCachedDeviceManager.mHearingAidDevicesNotAddedInCache.add(mCachedDevice2); + doAnswer((invocation) -> DEVICE_SUMMARY_1).when(mCachedDevice1).getConnectionSummary(); + doAnswer((invocation) -> DEVICE_SUMMARY_2).when(mCachedDevice2).getConnectionSummary(); + + assertThat(mCachedDeviceManager.getHearingAidPairDeviceSummary(mCachedDevice1)) + .isEqualTo(DEVICE_SUMMARY_2); + } + + /** + * Test to verify getHearingAidPairDeviceSummary() for hearing aid devices with different + * HiSyncId. + */ + @Test + public void testGetHearingAidPairDeviceSummary_bothHearingAidsNotPaired_returnsNull() { + mCachedDevice1.setHiSyncId(HISYNCID1); + mCachedDevice2.setHiSyncId(HISYNCID2); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mCachedDeviceManager.mHearingAidDevicesNotAddedInCache.add(mCachedDevice2); + doAnswer((invocation) -> DEVICE_SUMMARY_1).when(mCachedDevice1).getConnectionSummary(); + doAnswer((invocation) -> DEVICE_SUMMARY_2).when(mCachedDevice2).getConnectionSummary(); + + assertThat(mCachedDeviceManager.getHearingAidPairDeviceSummary(mCachedDevice1)) + .isEqualTo(null); + } + + /** + * Test to verify updateHearingAidsDevices(). + */ + @Test + public void testUpdateHearingAidDevices_hiSyncIdAvailable_setsHiSyncId() { + doAnswer((invocation) -> mHearingAidProfile).when(mLocalProfileManager) + .getHearingAidProfile(); + doAnswer((invocation) -> HISYNCID1).when(mHearingAidProfile).getHiSyncId(mDevice1); + mCachedDeviceManager.mCachedDevices.add(mCachedDevice1); + mCachedDeviceManager.updateHearingAidsDevices(mLocalProfileManager); + + // Assert that the mCachedDevice1 has an updated HiSyncId. + assertThat(mCachedDevice1.getHiSyncId()).isEqualTo(HISYNCID1); + } + + /** * Test to verify onBtClassChanged(). */ @Test diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java index 49f58a42d13e..7863fc588778 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java @@ -334,4 +334,40 @@ public class CachedBluetoothDeviceTest { mCachedDevice.onProfileStateChanged(mHfpProfile, BluetoothProfile.STATE_DISCONNECTED); assertThat(mCachedDevice.setActive()).isFalse(); } + + @Test + public void testIsA2dpDevice_isA2dpDevice() { + when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); + when(mA2dpProfile.getConnectionStatus(mDevice)). + thenReturn(BluetoothProfile.STATE_CONNECTED); + + assertThat(mCachedDevice.isA2dpDevice()).isTrue(); + } + + @Test + public void testIsA2dpDevice_isNotA2dpDevice() { + when(mProfileManager.getA2dpProfile()).thenReturn(mA2dpProfile); + when(mA2dpProfile.getConnectionStatus(mDevice)). + thenReturn(BluetoothProfile.STATE_DISCONNECTING); + + assertThat(mCachedDevice.isA2dpDevice()).isFalse(); + } + + @Test + public void testIsHfpDevice_isHfpDevice() { + when(mProfileManager.getHeadsetProfile()).thenReturn(mHfpProfile); + when(mHfpProfile.getConnectionStatus(mDevice)). + thenReturn(BluetoothProfile.STATE_CONNECTED); + + assertThat(mCachedDevice.isHfpDevice()).isTrue(); + } + + @Test + public void testIsHfpDevice_isNotHfpDevice() { + when(mProfileManager.getHeadsetProfile()).thenReturn(mHfpProfile); + when(mHfpProfile.getConnectionStatus(mDevice)). + thenReturn(BluetoothProfile.STATE_DISCONNECTING); + + assertThat(mCachedDevice.isHfpDevice()).isFalse(); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HeadsetProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HeadsetProfileTest.java index 117f4472825c..03b023b75f4a 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HeadsetProfileTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HeadsetProfileTest.java @@ -8,6 +8,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; +import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; import android.bluetooth.BluetoothProfile; import android.content.Context; @@ -31,7 +32,10 @@ public class HeadsetProfileTest { private LocalBluetoothProfileManager mProfileManager; @Mock private BluetoothHeadset mService; - + @Mock + private CachedBluetoothDevice mCachedBluetoothDevice; + @Mock + private BluetoothDevice mBluetoothDevice; private BluetoothProfile.ServiceListener mServiceListener; private HeadsetProfile mProfile; @@ -44,6 +48,7 @@ public class HeadsetProfileTest { mServiceListener = (BluetoothProfile.ServiceListener) invocation.getArguments()[1]; return null; }).when(mAdapter).getProfileProxy(any(Context.class), any(), eq(BluetoothProfile.HEADSET)); + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); mProfile = new HeadsetProfile(context, mAdapter, mDeviceManager, mProfileManager); mServiceListener.onServiceConnected(BluetoothProfile.HEADSET, mService); @@ -57,4 +62,17 @@ public class HeadsetProfileTest { when(mService.isAudioOn()).thenReturn(false); assertThat(mProfile.isAudioOn()).isFalse(); } + + @Test + public void testHeadsetProfile_shouldReturnAudioState() { + when(mService.getAudioState(mBluetoothDevice)). + thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED); + assertThat(mProfile.getAudioState(mBluetoothDevice)). + isEqualTo(BluetoothHeadset.STATE_AUDIO_DISCONNECTED); + + when(mService.getAudioState(mBluetoothDevice)). + thenReturn(BluetoothHeadset.STATE_AUDIO_CONNECTED); + assertThat(mProfile.getAudioState(mBluetoothDevice)). + isEqualTo(BluetoothHeadset.STATE_AUDIO_CONNECTED); + } } diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java index 4901192adbca..0a7d3fdb5973 100644 --- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java +++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java @@ -81,7 +81,7 @@ class ActivityManagerDebugConfig { static final boolean DEBUG_FOREGROUND_SERVICE = DEBUG_ALL || false; static final boolean DEBUG_SERVICE_EXECUTING = DEBUG_ALL || false; static final boolean DEBUG_STACK = DEBUG_ALL || false; - static final boolean DEBUG_STATES = DEBUG_ALL_ACTIVITIES || true; + static final boolean DEBUG_STATES = DEBUG_ALL_ACTIVITIES || false; static final boolean DEBUG_SWITCH = DEBUG_ALL || false; static final boolean DEBUG_TASKS = DEBUG_ALL || false; static final boolean DEBUG_TRANSITION = DEBUG_ALL || false; diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index f32717a3bea9..16c5969a9167 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -229,8 +229,6 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo private static final String ATTR_COMPONENTSPECIFIED = "component_specified"; static final String ACTIVITY_ICON_SUFFIX = "_activity_icon_"; - private static final int MAX_STORED_STATE_TRANSITIONS = 5; - final ActivityManagerService service; // owner final IApplicationToken.Stub appToken; // window manager token AppWindowContainerController mWindowContainerController; @@ -368,28 +366,6 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo private final Configuration mTmpConfig = new Configuration(); private final Rect mTmpBounds = new Rect(); - private final ArrayList<StateTransition> mRecentTransitions = new ArrayList<>(); - - // TODO(b/71506345): Remove once issue has been resolved. - private static class StateTransition { - final long time; - final ActivityState prev; - final ActivityState state; - final String reason; - - StateTransition(ActivityState prev, ActivityState state, String reason) { - time = System.currentTimeMillis(); - this.prev = prev; - this.state = state; - this.reason = reason; - } - - @Override - public String toString() { - return "[" + prev + "->" + state + ":" + reason + "@" + time + "]"; - } - } - private static String startingWindowStateToString(int state) { switch (state) { case STARTING_WINDOW_NOT_SHOWN: @@ -403,21 +379,6 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } } - String getLifecycleDescription(String reason) { - StringBuilder transitionBuilder = new StringBuilder(); - - for (int i = 0, size = mRecentTransitions.size(); i < size; ++i) { - transitionBuilder.append(mRecentTransitions.get(i)); - if (i + 1 < size) { - transitionBuilder.append(","); - } - } - - return "name= " + this + ", component=" + intent.getComponent().flattenToShortString() - + ", package=" + packageName + ", state=" + mState + ", reason=" + reason - + ", time=" + System.currentTimeMillis() + " transitions=" + transitionBuilder; - } - void dump(PrintWriter pw, String prefix) { final long now = SystemClock.uptimeMillis(); pw.print(prefix); pw.print("packageName="); pw.print(packageName); @@ -1658,15 +1619,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo return; } - final ActivityState prev = mState; mState = state; - if (mRecentTransitions.size() == MAX_STORED_STATE_TRANSITIONS) { - mRecentTransitions.remove(0); - } - - mRecentTransitions.add(new StateTransition(prev, state, reason)); - final TaskRecord parent = getTask(); if (parent != null) { @@ -1770,15 +1724,13 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo if (isState(STOPPED, STOPPING) && stack.mTranslucentActivityWaiting == null && mStackSupervisor.getResumedActivityLocked() != this) { // Capture reason before state change - final String reason = getLifecycleDescription("makeVisibleIfNeeded"); // An activity must be in the {@link PAUSING} state for the system to validate // the move to {@link PAUSED}. setState(PAUSING, "makeVisibleIfNeeded"); service.getLifecycleManager().scheduleTransaction(app.thread, appToken, PauseActivityItem.obtain(finishing, false /* userLeaving */, - configChangeFlags, false /* dontReport */) - .setDescription(reason)); + configChangeFlags, false /* dontReport */)); } } catch (Exception e) { // Just skip on any failure; we'll make it visible when it next restarts. @@ -2737,8 +2689,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo if (andResume) { lifecycleItem = ResumeActivityItem.obtain(service.isNextTransitionForward()); } else { - lifecycleItem = PauseActivityItem.obtain() - .setDescription(getLifecycleDescription("relaunchActivityLocked")); + lifecycleItem = PauseActivityItem.obtain(); } final ClientTransaction transaction = ClientTransaction.obtain(app.thread, appToken); transaction.addCallback(callbackItem); diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index eb482c1b14e2..e54e6453b698 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -1487,8 +1487,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai mService.getLifecycleManager().scheduleTransaction(prev.app.thread, prev.appToken, PauseActivityItem.obtain(prev.finishing, userLeaving, - prev.configChangeFlags, pauseImmediately).setDescription( - prev.getLifecycleDescription("startPausingLocked"))); + prev.configChangeFlags, pauseImmediately)); } catch (Exception e) { // Ignore exception, if process died other code will cleanup. Slog.w(TAG, "Exception thrown during pause", e); @@ -2694,9 +2693,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai next.clearOptionsLocked(); transaction.setLifecycleStateRequest( ResumeActivityItem.obtain(next.app.repProcState, - mService.isNextTransitionForward()) - .setDescription(next.getLifecycleDescription( - "resumeTopActivityInnerLocked"))); + mService.isNextTransitionForward())); mService.getLifecycleManager().scheduleTransaction(transaction); if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed " @@ -3480,8 +3477,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai EventLogTags.writeAmStopActivity( r.userId, System.identityHashCode(r), r.shortComponentName); mService.getLifecycleManager().scheduleTransaction(r.app.thread, r.appToken, - StopActivityItem.obtain(r.visible, r.configChangeFlags) - .setDescription(r.getLifecycleDescription("stopActivityLocked"))); + StopActivityItem.obtain(r.visible, r.configChangeFlags)); if (shouldSleepOrShutDownActivities()) { r.setSleeping(true); } @@ -4308,9 +4304,7 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai try { if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + r); mService.getLifecycleManager().scheduleTransaction(r.app.thread, r.appToken, - DestroyActivityItem.obtain(r.finishing, r.configChangeFlags) - .setDescription( - r.getLifecycleDescription("destroyActivityLocked:" + reason))); + DestroyActivityItem.obtain(r.finishing, r.configChangeFlags)); } catch (Exception e) { // We can just ignore exceptions here... if the process // has crashed, our death notification will clean things diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 28692fc9e4ca..6a3587c69dfb 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -1466,11 +1466,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // Set desired final state. final ActivityLifecycleItem lifecycleItem; if (andResume) { - lifecycleItem = ResumeActivityItem.obtain(mService.isNextTransitionForward()) - .setDescription(r.getLifecycleDescription("realStartActivityLocked")); + lifecycleItem = ResumeActivityItem.obtain(mService.isNextTransitionForward()); } else { - lifecycleItem = PauseActivityItem.obtain() - .setDescription(r.getLifecycleDescription("realStartActivityLocked")); + lifecycleItem = PauseActivityItem.obtain(); } clientTransaction.setLifecycleStateRequest(lifecycleItem); diff --git a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java index ba7fe7846507..961d3db867e9 100644 --- a/services/core/java/com/android/server/notification/ScheduleConditionProvider.java +++ b/services/core/java/com/android/server/notification/ScheduleConditionProvider.java @@ -185,6 +185,8 @@ public class ScheduleConditionProvider extends SystemConditionProviderService { @GuardedBy("mSubscriptions") Condition evaluateSubscriptionLocked(Uri conditionId, ScheduleCalendar cal, long now, long nextUserAlarmTime) { + if (DEBUG) Slog.d(TAG, String.format("evaluateSubscriptionLocked cal=%s, now=%s, " + + "nextUserAlarmTime=%s", cal, ts(now), ts(nextUserAlarmTime))); Condition condition; if (cal == null) { condition = createCondition(conditionId, Condition.STATE_ERROR, "!invalidId"); diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index e098de90cdef..ca8c6d69cfc3 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -2758,8 +2758,8 @@ public class TelephonyManager { * physical slot index 0, to the logical slot 1. The index of the array means the index of the * logical slots. * - * @param physicalSlots Index i in the array representing physical slot for phone i. The array - * size should be same as {@link #getPhoneCount()}. + * @param physicalSlots The content of the array represents the physical slot index. The array + * size should be same as {@link #getUiccSlotsInfo()}. * @return boolean Return true if the switch succeeds, false if the switch fails. * @hide */ diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java index 38f9745a58b0..11411778a9ab 100644 --- a/telephony/java/android/telephony/euicc/EuiccCardManager.java +++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java @@ -623,7 +623,7 @@ public class EuiccCardManager { } /** - * Lists all notifications of the given {@code notificationEvents}. + * Lists all notifications of the given {@code events}. * * @param cardId The Id of the eUICC. * @param events bits of the event types ({@link EuiccNotification.Event}) to list. |