Loading android/app/jni/com_android_bluetooth_hfp.cpp +15 −0 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ static jmethodID method_onUnknownAt; static jmethodID method_onKeyPressed; static jmethodID method_onAtBind; static jmethodID method_onAtBiev; static jmethodID method_onAtBia; static bluetooth::headset::Interface* sBluetoothHfpInterface = nullptr; static std::shared_timed_mutex interface_mutex; Loading Loading @@ -366,6 +367,19 @@ class JniHeadsetCallbacks : bluetooth::headset::Callbacks { sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtBiev, ind_id, (jint)ind_value, addr.get()); } void AtBiaCallback(bool service, bool roam, bool signal, bool battery, RawAddress* bd_addr) override { std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || !mCallbacksObj) return; ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr)); if (addr.get() == nullptr) return; sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtBia, service, roam, signal, battery, addr.get()); } }; static void classInitNative(JNIEnv* env, jclass clazz) { Loading Loading @@ -396,6 +410,7 @@ static void classInitNative(JNIEnv* env, jclass clazz) { method_onAtBind = env->GetMethodID(clazz, "onATBind", "(Ljava/lang/String;[B)V"); method_onAtBiev = env->GetMethodID(clazz, "onATBiev", "(II[B)V"); method_onAtBia = env->GetMethodID(clazz, "onAtBia", "(ZZZZ[B)V"); ALOGI("%s: succeeds", __func__); } Loading android/app/src/com/android/bluetooth/hfp/HeadsetAgIndicatorEnableState.java 0 → 100644 +71 −0 Original line number Diff line number Diff line /* * Copyright 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.bluetooth.hfp; /** * An object that represents AG indicator enable states */ public class HeadsetAgIndicatorEnableState extends HeadsetMessageObject { public boolean service; public boolean roam; public boolean signal; public boolean battery; HeadsetAgIndicatorEnableState(boolean newService, boolean newRoam, boolean newSignal, boolean newBattery) { service = newService; roam = newRoam; signal = newSignal; battery = newBattery; } @Override public boolean equals(Object obj) { if (!(obj instanceof HeadsetAgIndicatorEnableState)) { return false; } HeadsetAgIndicatorEnableState other = (HeadsetAgIndicatorEnableState) obj; return service == other.service && roam == other.roam && signal == other.signal && battery == other.battery; } @Override public int hashCode() { int result = 0; if (service) result += 1; if (roam) result += 2; if (signal) result += 4; if (battery) result += 8; return result; } @Override public void buildString(StringBuilder builder) { if (builder == null) { return; } builder.append(this.getClass().getSimpleName()) .append("[service=") .append(service) .append(", roam=") .append(roam) .append(", signal=") .append(signal) .append(", battery=") .append(battery) .append("]"); } } android/app/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java +10 −0 Original line number Diff line number Diff line Loading @@ -197,6 +197,16 @@ public class HeadsetNativeInterface { sendMessageToService(event); } private void onAtBia(boolean service, boolean roam, boolean signal, boolean battery, byte[] address) { HeadsetAgIndicatorEnableState agIndicatorEnableState = new HeadsetAgIndicatorEnableState(service, roam, signal, battery); HeadsetStackEvent event = new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIA, agIndicatorEnableState, getDevice(address)); sendMessageToService(event); } // Native wrappers to help unit testing /** Loading android/app/src/com/android/bluetooth/hfp/HeadsetPhoneState.java +58 −25 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.bluetooth.hfp; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; Loading @@ -33,6 +34,7 @@ import android.util.Log; import com.android.internal.telephony.IccCardConstants; import com.android.internal.telephony.TelephonyIntents; import java.util.HashMap; import java.util.Objects; Loading Loading @@ -71,7 +73,7 @@ public class HeadsetPhoneState { // HFP 1.6 CIND battchg value private int mCindBatteryCharge; private boolean mListening; private final HashMap<BluetoothDevice, Integer> mDeviceEventMap = new HashMap<>(); private PhoneStateListener mPhoneStateListener; private final OnSubscriptionsChangedListener mOnSubscriptionsChangedListener; Loading @@ -95,7 +97,10 @@ public class HeadsetPhoneState { * Cleanup this instance. Instance can no longer be used after calling this method. */ public void cleanup() { listenForPhoneState(false); synchronized (mDeviceEventMap) { mDeviceEventMap.clear(); stopListenForPhoneState(); } mSubscriptionManager.removeOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener); } Loading @@ -104,45 +109,71 @@ public class HeadsetPhoneState { return "HeadsetPhoneState [mTelephonyServiceAvailability=" + mCindService + ", mNumActive=" + mNumActive + ", mCallState=" + mCallState + ", mNumHeld=" + mNumHeld + ", mSignal=" + mCindSignal + ", mRoam=" + mCindRoam + ", mBatteryCharge=" + mCindBatteryCharge + ", mListening=" + mListening + "]"; + mCindBatteryCharge + ", TelephonyEvents=" + getTelephonyEventsToListen() + "]"; } private int getTelephonyEventsToListen() { synchronized (mDeviceEventMap) { return mDeviceEventMap.values() .stream() .reduce(PhoneStateListener.LISTEN_NONE, (a, b) -> a | b); } } /** * Start or stop listening for phone state change * @param start True to start, False to stop * * @param device remote device that subscribes to this phone state update * @param events events in {@link PhoneStateListener} to listen to */ @VisibleForTesting public void listenForPhoneState(boolean start) { synchronized (mTelephonyManager) { if (start) { startListenForPhoneState(); public void listenForPhoneState(BluetoothDevice device, int events) { synchronized (mDeviceEventMap) { int prevEvents = getTelephonyEventsToListen(); if (events == PhoneStateListener.LISTEN_NONE) { mDeviceEventMap.remove(device); } else { mDeviceEventMap.put(device, events); } int updatedEvents = getTelephonyEventsToListen(); if (prevEvents != updatedEvents) { stopListenForPhoneState(); startListenForPhoneState(); } } } private void startListenForPhoneState() { if (!mListening) { if (mPhoneStateListener != null) { Log.w(TAG, "startListenForPhoneState, already listening"); return; } int events = getTelephonyEventsToListen(); if (events == PhoneStateListener.LISTEN_NONE) { Log.w(TAG, "startListenForPhoneState, no event to listen"); return; } int subId = SubscriptionManager.getDefaultSubscriptionId(); if (SubscriptionManager.isValidSubscriptionId(subId)) { mPhoneStateListener = new HeadsetPhoneStateListener(subId, mHeadsetService.getStateMachinesThreadLooper()); mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS); mListening = true; } else { if (!SubscriptionManager.isValidSubscriptionId(subId)) { // Will retry listening for phone state in onSubscriptionsChanged() callback Log.w(TAG, "startListenForPhoneState, invalid subscription ID " + subId); return; } } Log.i(TAG, "startListenForPhoneState(), subId=" + subId + ", enabled_events=" + events); mPhoneStateListener = new HeadsetPhoneStateListener(subId, mHeadsetService.getStateMachinesThreadLooper()); mTelephonyManager.listen(mPhoneStateListener, events); } private void stopListenForPhoneState() { if (mListening) { mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); mListening = false; if (mPhoneStateListener == null) { Log.i(TAG, "stopListenForPhoneState(), no listener indicates nothing is listening"); return; } Log.i(TAG, "stopListenForPhoneState(), stopping listener, enabled_events=" + getTelephonyEventsToListen()); mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); mPhoneStateListener = null; } int getCindService() { Loading Loading @@ -223,8 +254,10 @@ public class HeadsetPhoneState { @Override public void onSubscriptionsChanged() { listenForPhoneState(false); listenForPhoneState(true); synchronized (mDeviceEventMap) { stopListenForPhoneState(); startListenForPhoneState(); } } } Loading android/app/src/com/android/bluetooth/hfp/HeadsetService.java +2 −1 Original line number Diff line number Diff line Loading @@ -200,7 +200,8 @@ public class HeadsetService extends ProfileService { * * @return {@link Looper} for the state machine thread */ Looper getStateMachinesThreadLooper() { @VisibleForTesting public Looper getStateMachinesThreadLooper() { return mStateMachinesThread.getLooper(); } Loading Loading
android/app/jni/com_android_bluetooth_hfp.cpp +15 −0 Original line number Diff line number Diff line Loading @@ -49,6 +49,7 @@ static jmethodID method_onUnknownAt; static jmethodID method_onKeyPressed; static jmethodID method_onAtBind; static jmethodID method_onAtBiev; static jmethodID method_onAtBia; static bluetooth::headset::Interface* sBluetoothHfpInterface = nullptr; static std::shared_timed_mutex interface_mutex; Loading Loading @@ -366,6 +367,19 @@ class JniHeadsetCallbacks : bluetooth::headset::Callbacks { sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtBiev, ind_id, (jint)ind_value, addr.get()); } void AtBiaCallback(bool service, bool roam, bool signal, bool battery, RawAddress* bd_addr) override { std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex); CallbackEnv sCallbackEnv(__func__); if (!sCallbackEnv.valid() || !mCallbacksObj) return; ScopedLocalRef<jbyteArray> addr(sCallbackEnv.get(), marshall_bda(bd_addr)); if (addr.get() == nullptr) return; sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onAtBia, service, roam, signal, battery, addr.get()); } }; static void classInitNative(JNIEnv* env, jclass clazz) { Loading Loading @@ -396,6 +410,7 @@ static void classInitNative(JNIEnv* env, jclass clazz) { method_onAtBind = env->GetMethodID(clazz, "onATBind", "(Ljava/lang/String;[B)V"); method_onAtBiev = env->GetMethodID(clazz, "onATBiev", "(II[B)V"); method_onAtBia = env->GetMethodID(clazz, "onAtBia", "(ZZZZ[B)V"); ALOGI("%s: succeeds", __func__); } Loading
android/app/src/com/android/bluetooth/hfp/HeadsetAgIndicatorEnableState.java 0 → 100644 +71 −0 Original line number Diff line number Diff line /* * Copyright 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.bluetooth.hfp; /** * An object that represents AG indicator enable states */ public class HeadsetAgIndicatorEnableState extends HeadsetMessageObject { public boolean service; public boolean roam; public boolean signal; public boolean battery; HeadsetAgIndicatorEnableState(boolean newService, boolean newRoam, boolean newSignal, boolean newBattery) { service = newService; roam = newRoam; signal = newSignal; battery = newBattery; } @Override public boolean equals(Object obj) { if (!(obj instanceof HeadsetAgIndicatorEnableState)) { return false; } HeadsetAgIndicatorEnableState other = (HeadsetAgIndicatorEnableState) obj; return service == other.service && roam == other.roam && signal == other.signal && battery == other.battery; } @Override public int hashCode() { int result = 0; if (service) result += 1; if (roam) result += 2; if (signal) result += 4; if (battery) result += 8; return result; } @Override public void buildString(StringBuilder builder) { if (builder == null) { return; } builder.append(this.getClass().getSimpleName()) .append("[service=") .append(service) .append(", roam=") .append(roam) .append(", signal=") .append(signal) .append(", battery=") .append(battery) .append("]"); } }
android/app/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java +10 −0 Original line number Diff line number Diff line Loading @@ -197,6 +197,16 @@ public class HeadsetNativeInterface { sendMessageToService(event); } private void onAtBia(boolean service, boolean roam, boolean signal, boolean battery, byte[] address) { HeadsetAgIndicatorEnableState agIndicatorEnableState = new HeadsetAgIndicatorEnableState(service, roam, signal, battery); HeadsetStackEvent event = new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIA, agIndicatorEnableState, getDevice(address)); sendMessageToService(event); } // Native wrappers to help unit testing /** Loading
android/app/src/com/android/bluetooth/hfp/HeadsetPhoneState.java +58 −25 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.bluetooth.hfp; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; Loading @@ -33,6 +34,7 @@ import android.util.Log; import com.android.internal.telephony.IccCardConstants; import com.android.internal.telephony.TelephonyIntents; import java.util.HashMap; import java.util.Objects; Loading Loading @@ -71,7 +73,7 @@ public class HeadsetPhoneState { // HFP 1.6 CIND battchg value private int mCindBatteryCharge; private boolean mListening; private final HashMap<BluetoothDevice, Integer> mDeviceEventMap = new HashMap<>(); private PhoneStateListener mPhoneStateListener; private final OnSubscriptionsChangedListener mOnSubscriptionsChangedListener; Loading @@ -95,7 +97,10 @@ public class HeadsetPhoneState { * Cleanup this instance. Instance can no longer be used after calling this method. */ public void cleanup() { listenForPhoneState(false); synchronized (mDeviceEventMap) { mDeviceEventMap.clear(); stopListenForPhoneState(); } mSubscriptionManager.removeOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener); } Loading @@ -104,45 +109,71 @@ public class HeadsetPhoneState { return "HeadsetPhoneState [mTelephonyServiceAvailability=" + mCindService + ", mNumActive=" + mNumActive + ", mCallState=" + mCallState + ", mNumHeld=" + mNumHeld + ", mSignal=" + mCindSignal + ", mRoam=" + mCindRoam + ", mBatteryCharge=" + mCindBatteryCharge + ", mListening=" + mListening + "]"; + mCindBatteryCharge + ", TelephonyEvents=" + getTelephonyEventsToListen() + "]"; } private int getTelephonyEventsToListen() { synchronized (mDeviceEventMap) { return mDeviceEventMap.values() .stream() .reduce(PhoneStateListener.LISTEN_NONE, (a, b) -> a | b); } } /** * Start or stop listening for phone state change * @param start True to start, False to stop * * @param device remote device that subscribes to this phone state update * @param events events in {@link PhoneStateListener} to listen to */ @VisibleForTesting public void listenForPhoneState(boolean start) { synchronized (mTelephonyManager) { if (start) { startListenForPhoneState(); public void listenForPhoneState(BluetoothDevice device, int events) { synchronized (mDeviceEventMap) { int prevEvents = getTelephonyEventsToListen(); if (events == PhoneStateListener.LISTEN_NONE) { mDeviceEventMap.remove(device); } else { mDeviceEventMap.put(device, events); } int updatedEvents = getTelephonyEventsToListen(); if (prevEvents != updatedEvents) { stopListenForPhoneState(); startListenForPhoneState(); } } } private void startListenForPhoneState() { if (!mListening) { if (mPhoneStateListener != null) { Log.w(TAG, "startListenForPhoneState, already listening"); return; } int events = getTelephonyEventsToListen(); if (events == PhoneStateListener.LISTEN_NONE) { Log.w(TAG, "startListenForPhoneState, no event to listen"); return; } int subId = SubscriptionManager.getDefaultSubscriptionId(); if (SubscriptionManager.isValidSubscriptionId(subId)) { mPhoneStateListener = new HeadsetPhoneStateListener(subId, mHeadsetService.getStateMachinesThreadLooper()); mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS); mListening = true; } else { if (!SubscriptionManager.isValidSubscriptionId(subId)) { // Will retry listening for phone state in onSubscriptionsChanged() callback Log.w(TAG, "startListenForPhoneState, invalid subscription ID " + subId); return; } } Log.i(TAG, "startListenForPhoneState(), subId=" + subId + ", enabled_events=" + events); mPhoneStateListener = new HeadsetPhoneStateListener(subId, mHeadsetService.getStateMachinesThreadLooper()); mTelephonyManager.listen(mPhoneStateListener, events); } private void stopListenForPhoneState() { if (mListening) { mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); mListening = false; if (mPhoneStateListener == null) { Log.i(TAG, "stopListenForPhoneState(), no listener indicates nothing is listening"); return; } Log.i(TAG, "stopListenForPhoneState(), stopping listener, enabled_events=" + getTelephonyEventsToListen()); mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE); mPhoneStateListener = null; } int getCindService() { Loading Loading @@ -223,8 +254,10 @@ public class HeadsetPhoneState { @Override public void onSubscriptionsChanged() { listenForPhoneState(false); listenForPhoneState(true); synchronized (mDeviceEventMap) { stopListenForPhoneState(); startListenForPhoneState(); } } } Loading
android/app/src/com/android/bluetooth/hfp/HeadsetService.java +2 −1 Original line number Diff line number Diff line Loading @@ -200,7 +200,8 @@ public class HeadsetService extends ProfileService { * * @return {@link Looper} for the state machine thread */ Looper getStateMachinesThreadLooper() { @VisibleForTesting public Looper getStateMachinesThreadLooper() { return mStateMachinesThread.getLooper(); } Loading