From 89e3d1bb7941993206861fe9115778f816262c07 Mon Sep 17 00:00:00 2001 From: erfanian Date: Wed, 2 May 2018 18:50:59 -0700 Subject: More refactoring Test: unit PiperOrigin-RevId: 195188099 Change-Id: Ie2273229c257a9a767715831c15d90a6985fdda8 --- java/com/android/incallui/InCallActivity.java | 3 +- java/com/android/incallui/call/DialerCall.java | 5 -- .../incallui/speakeasy/SpeakEasyCallManager.java | 12 ++++ .../speakeasy/SpeakEasyCallManagerStub.java | 15 +++++ .../incallui/speakeasy/runtime/Constraints.java | 74 ---------------------- 5 files changed, 29 insertions(+), 80 deletions(-) delete mode 100644 java/com/android/incallui/speakeasy/runtime/Constraints.java (limited to 'java/com/android/incallui') diff --git a/java/com/android/incallui/InCallActivity.java b/java/com/android/incallui/InCallActivity.java index 5ac6b5029..98f001925 100644 --- a/java/com/android/incallui/InCallActivity.java +++ b/java/com/android/incallui/InCallActivity.java @@ -1504,7 +1504,8 @@ public class InCallActivity extends TransactionSafeFragmentActivity call.getVideoTech().isSelfManagedCamera(), shouldAllowAnswerAndRelease(call), CallList.getInstance().getBackgroundCall() != null, - call.isSpeakEasyEligible()); + getSpeakEasyCallManager().isAvailable(getApplicationContext()) + && call.isSpeakEasyEligible()); transaction.add(R.id.main, answerScreen.getAnswerScreenFragment(), Tags.ANSWER_SCREEN); Logger.get(this).logScreenView(ScreenEvent.Type.INCOMING_CALL, this); diff --git a/java/com/android/incallui/call/DialerCall.java b/java/com/android/incallui/call/DialerCall.java index 1a0de1960..70678dbac 100644 --- a/java/com/android/incallui/call/DialerCall.java +++ b/java/com/android/incallui/call/DialerCall.java @@ -86,7 +86,6 @@ import com.android.incallui.audiomode.AudioModeProvider; import com.android.incallui.call.state.DialerCallState; import com.android.incallui.latencyreport.LatencyReport; import com.android.incallui.rtt.protocol.RttChatMessage; -import com.android.incallui.speakeasy.runtime.Constraints; import com.android.incallui.videotech.VideoTech; import com.android.incallui.videotech.VideoTech.VideoTechListener; import com.android.incallui.videotech.duo.DuoVideoTech; @@ -1697,10 +1696,6 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa /** Indicates the call is eligible for SpeakEasy */ public boolean isSpeakEasyEligible() { - if (!Constraints.isAvailable(context)) { - return false; - } - return !isPotentialEmergencyCallback() && !isEmergencyCall() && !isActiveRttCall() diff --git a/java/com/android/incallui/speakeasy/SpeakEasyCallManager.java b/java/com/android/incallui/speakeasy/SpeakEasyCallManager.java index f2721da7d..8a815d385 100644 --- a/java/com/android/incallui/speakeasy/SpeakEasyCallManager.java +++ b/java/com/android/incallui/speakeasy/SpeakEasyCallManager.java @@ -16,6 +16,7 @@ package com.android.incallui.speakeasy; +import android.content.Context; import android.support.annotation.NonNull; import android.support.v4.app.Fragment; import com.android.incallui.call.DialerCall; @@ -37,4 +38,15 @@ public interface SpeakEasyCallManager { * @param call The call which has been removed. */ void onCallRemoved(@NonNull DialerCall call); + + /** + * Indicates the feature is available. + * + * @param context The application context. + */ + boolean isAvailable(@NonNull Context context); + + /** Returns the config provider flag associated with the feature. */ + @NonNull + String getConfigProviderFlag(); } diff --git a/java/com/android/incallui/speakeasy/SpeakEasyCallManagerStub.java b/java/com/android/incallui/speakeasy/SpeakEasyCallManagerStub.java index 9e58ce18f..a0409737b 100644 --- a/java/com/android/incallui/speakeasy/SpeakEasyCallManagerStub.java +++ b/java/com/android/incallui/speakeasy/SpeakEasyCallManagerStub.java @@ -16,6 +16,8 @@ package com.android.incallui.speakeasy; +import android.content.Context; +import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import com.android.incallui.call.DialerCall; @@ -38,4 +40,17 @@ public class SpeakEasyCallManagerStub implements SpeakEasyCallManager { /** Always inert in the stub. */ @Override public void onCallRemoved(DialerCall call) {} + + /** Always returns false. */ + @Override + public boolean isAvailable(@NonNull Context unused) { + return false; + } + + /** Always returns a stub string. */ + @NonNull + @Override + public String getConfigProviderFlag() { + return "not_yet_implmented"; + } } diff --git a/java/com/android/incallui/speakeasy/runtime/Constraints.java b/java/com/android/incallui/speakeasy/runtime/Constraints.java deleted file mode 100644 index 1206d599c..000000000 --- a/java/com/android/incallui/speakeasy/runtime/Constraints.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 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.incallui.speakeasy.runtime; - -import android.annotation.TargetApi; -import android.content.Context; -import android.os.Build.VERSION_CODES; -import android.support.annotation.NonNull; -import android.support.annotation.VisibleForTesting; -import android.support.v4.os.BuildCompat; -import android.support.v4.os.UserManagerCompat; -import com.android.dialer.common.Assert; -import com.android.dialer.common.LogUtil; -import com.android.dialer.configprovider.ConfigProviderBindings; -import com.android.dialer.util.PermissionsUtil; - -/** Preconditions for the use of SpeakEasyModule */ -public final class Constraints { - - @VisibleForTesting public static final String SPEAK_EASY_ENABLED = "speak_easy_enabled"; - private static final String[] REQUIRED_PERMISSIONS = { - - }; - - // Non-instantiatable. - private Constraints() {} - - public static boolean isAvailable(@NonNull Context context) { - Assert.isNotNull(context); - - return isServerConfigEnabled(context) - && isUserUnlocked(context) - && meetsPlatformSdkFloor() - && hasNecessaryPermissions(context); - } - - private static boolean isServerConfigEnabled(@NonNull Context context) { - return ConfigProviderBindings.get(context).getBoolean(SPEAK_EASY_ENABLED, false); - } - - private static boolean isUserUnlocked(@NonNull Context context) { - return UserManagerCompat.isUserUnlocked(context); - } - - private static boolean meetsPlatformSdkFloor() { - return BuildCompat.isAtLeastP(); - } - - @SuppressWarnings("AndroidApiChecker") // Use of Java 8 APIs. - @TargetApi(VERSION_CODES.N) - private static boolean hasNecessaryPermissions(@NonNull Context context) { - for (String permission : REQUIRED_PERMISSIONS) { - if (!PermissionsUtil.hasPermission(context, permission)) { - LogUtil.i("Constraints.hasNecessaryPermissions", "missing permission: %s ", permission); - return false; - } - } - return true; - } -} -- cgit v1.2.3 From c1401221314601a479a7ad293cc382416f2304f3 Mon Sep 17 00:00:00 2001 From: maxwelb Date: Thu, 3 May 2018 11:51:02 -0700 Subject: Add spam status tests for CallList#onCallAdded There are some upcoming changes to CallList#onCallAdded, so this CL adds the tests needed to verify that behavior doesn't regress. This became something of a rabbit hole because: 1) CallListTest needed to use a FakeSpam so spam status could be changed 2) FakeSpam needed updates so it wouldn't crash when CallList calls it. This was implemented as a no-op for simplicity. It also needed to support the single number lookup method. 3) FakeCall needed updates to support setting the details' extras (needed tests) 4) ShadowCall needed updates to support setting the details' extras 5) DetailsProperty needed a new value 6) There was an old TODO to use a string that's now visible in the SDK 7) SharedPrefConfigProvider needed a new method to set the emergency call window long. Bug: 33779976,79171382 Test: Tap PiperOrigin-RevId: 195287258 Change-Id: Ib8b9fde35263183393e9bcbdcf73eeada24f28eb --- java/com/android/incallui/call/DialerCall.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'java/com/android/incallui') diff --git a/java/com/android/incallui/call/DialerCall.java b/java/com/android/incallui/call/DialerCall.java index 70678dbac..9e1e17e2a 100644 --- a/java/com/android/incallui/call/DialerCall.java +++ b/java/com/android/incallui/call/DialerCall.java @@ -50,7 +50,6 @@ import android.telecom.VideoProfile; import android.text.TextUtils; import android.widget.Toast; import com.android.contacts.common.compat.CallCompat; -import com.android.contacts.common.compat.telecom.TelecomManagerCompat; import com.android.dialer.assisteddialing.ConcreteCreator; import com.android.dialer.assisteddialing.TransformationInfo; import com.android.dialer.callintent.CallInitiationType; @@ -117,8 +116,11 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa public static final int PROPERTY_CODEC_KNOWN = 0x04000000; private static final String ID_PREFIX = "DialerCall_"; - private static final String CONFIG_EMERGENCY_CALLBACK_WINDOW_MILLIS = + + @VisibleForTesting + public static final String CONFIG_EMERGENCY_CALLBACK_WINDOW_MILLIS = "emergency_callback_window_millis"; + private static int idCounter = 0; /** @@ -821,10 +823,9 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa // We want to treat any incoming call that arrives a short time after an outgoing emergency call // as a potential emergency callback. if (getExtras() != null - && getExtras().getLong(TelecomManagerCompat.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0) - > 0) { + && getExtras().getLong(Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0) > 0) { long lastEmergencyCallMillis = - getExtras().getLong(TelecomManagerCompat.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0); + getExtras().getLong(Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS, 0); if (isInEmergencyCallbackWindow(lastEmergencyCallMillis)) { return true; } -- cgit v1.2.3 From 49f90b332c6e716196276dab0f3d7e21b56b696e Mon Sep 17 00:00:00 2001 From: wangqi Date: Thu, 3 May 2018 12:34:08 -0700 Subject: Add test to verify no crash on multiple DialerCall#onRemovedFromCallList. This change also add a null check for RttCall when saving RTT transcript. Test: DialerCallTest PiperOrigin-RevId: 195293823 Change-Id: I67cf3410480ac106933486d9d31f0fbb3183b074 --- java/com/android/incallui/call/DialerCall.java | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'java/com/android/incallui') diff --git a/java/com/android/incallui/call/DialerCall.java b/java/com/android/incallui/call/DialerCall.java index 9e1e17e2a..77e2ea30b 100644 --- a/java/com/android/incallui/call/DialerCall.java +++ b/java/com/android/incallui/call/DialerCall.java @@ -1058,6 +1058,7 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa } @TargetApi(28) + @Nullable public RttCall getRttCall() { if (!isActiveRttCall()) { return null; @@ -1111,16 +1112,18 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa if (!BuildCompat.isAtLeastP()) { return; } - // Save any remaining text in the buffer that's not shown by UI yet. - // This may happen when the call is switched to background before disconnect. - try { - String messageLeft = getRttCall().readImmediately(); - if (!TextUtils.isEmpty(messageLeft)) { - rttTranscript = - RttChatMessage.getRttTranscriptWithNewRemoteMessage(rttTranscript, messageLeft); + if (getRttCall() != null) { + // Save any remaining text in the buffer that's not shown by UI yet. + // This may happen when the call is switched to background before disconnect. + try { + String messageLeft = getRttCall().readImmediately(); + if (!TextUtils.isEmpty(messageLeft)) { + rttTranscript = + RttChatMessage.getRttTranscriptWithNewRemoteMessage(rttTranscript, messageLeft); + } + } catch (IOException e) { + LogUtil.e("DialerCall.saveRttTranscript", "error when reading remaining message", e); } - } catch (IOException e) { - LogUtil.e("DialerCall.saveRttTranscript", "error when reading remaining message", e); } // Don't save transcript if it's empty. if (rttTranscript.getMessagesCount() == 0) { @@ -1662,7 +1665,6 @@ public class DialerCall implements VideoTechListener, StateChangedListener, Capa if (videoTechManager != null) { videoTechManager.dispatchRemovedFromCallList(); } - // TODO(a bug): Add tests for it to make sure no crash on subsequent call to this method. // TODO(wangqi): Consider moving this to a DialerCallListener. if (rttTranscript != null && !isCallRemoved) { saveRttTranscript(); -- cgit v1.2.3 From fb2154107b6e0c29d89dd530fe05e2f94b3ae27a Mon Sep 17 00:00:00 2001 From: wangqi Date: Thu, 3 May 2018 18:02:10 -0700 Subject: Update audio route after user select different audio route. Bug: 79195339 Test: manual PiperOrigin-RevId: 195344623 Change-Id: I64225b781c84d363848a15bcdaa351585dce6b04 --- .../android/incallui/rtt/impl/AudioSelectMenu.java | 36 ++++++++++++---------- .../android/incallui/rtt/impl/RttChatFragment.java | 6 +++- 2 files changed, 25 insertions(+), 17 deletions(-) (limited to 'java/com/android/incallui') diff --git a/java/com/android/incallui/rtt/impl/AudioSelectMenu.java b/java/com/android/incallui/rtt/impl/AudioSelectMenu.java index 01c3950e9..1c83637ea 100644 --- a/java/com/android/incallui/rtt/impl/AudioSelectMenu.java +++ b/java/com/android/incallui/rtt/impl/AudioSelectMenu.java @@ -17,8 +17,6 @@ package com.android.incallui.rtt.impl; import android.content.Context; -import android.content.res.ColorStateList; -import android.graphics.PorterDuff.Mode; import android.telecom.CallAudioState; import android.view.View; import android.widget.PopupWindow; @@ -28,8 +26,11 @@ import com.android.incallui.incall.protocol.InCallButtonUiDelegate; public class AudioSelectMenu extends PopupWindow { private final InCallButtonUiDelegate inCallButtonUiDelegate; - private final Context context; private final OnButtonClickListener onButtonClickListener; + private final RttCheckableButton bluetoothButton; + private final RttCheckableButton speakerButton; + private final RttCheckableButton headsetButton; + private final RttCheckableButton earpieceButton; interface OnButtonClickListener { void onBackPressed(); @@ -40,7 +41,6 @@ public class AudioSelectMenu extends PopupWindow { InCallButtonUiDelegate inCallButtonUiDelegate, OnButtonClickListener onButtonClickListener) { super(context, null, 0, R.style.OverflowMenu); - this.context = context; this.inCallButtonUiDelegate = inCallButtonUiDelegate; this.onButtonClickListener = onButtonClickListener; View view = View.inflate(context, R.layout.audio_route, null); @@ -55,28 +55,32 @@ public class AudioSelectMenu extends PopupWindow { this.onButtonClickListener.onBackPressed(); }); CallAudioState audioState = inCallButtonUiDelegate.getCurrentAudioState(); - initItem( - view.findViewById(R.id.audioroute_bluetooth), CallAudioState.ROUTE_BLUETOOTH, audioState); - initItem(view.findViewById(R.id.audioroute_speaker), CallAudioState.ROUTE_SPEAKER, audioState); - initItem( - view.findViewById(R.id.audioroute_headset), CallAudioState.ROUTE_WIRED_HEADSET, audioState); - initItem( - view.findViewById(R.id.audioroute_earpiece), CallAudioState.ROUTE_EARPIECE, audioState); + bluetoothButton = view.findViewById(R.id.audioroute_bluetooth); + speakerButton = view.findViewById(R.id.audioroute_speaker); + headsetButton = view.findViewById(R.id.audioroute_headset); + earpieceButton = view.findViewById(R.id.audioroute_earpiece); + initItem(bluetoothButton, CallAudioState.ROUTE_BLUETOOTH, audioState); + initItem(speakerButton, CallAudioState.ROUTE_SPEAKER, audioState); + initItem(headsetButton, CallAudioState.ROUTE_WIRED_HEADSET, audioState); + initItem(earpieceButton, CallAudioState.ROUTE_EARPIECE, audioState); } private void initItem(RttCheckableButton item, final int itemRoute, CallAudioState audioState) { - int selectedColor = - context.getColor(com.android.incallui.audioroute.R.color.dialer_theme_color); if ((audioState.getSupportedRouteMask() & itemRoute) == 0) { item.setVisibility(View.GONE); } else if (audioState.getRoute() == itemRoute) { - item.setTextColor(selectedColor); - item.setCompoundDrawableTintList(ColorStateList.valueOf(selectedColor)); - item.setCompoundDrawableTintMode(Mode.SRC_ATOP); + item.setChecked(true); } item.setOnClickListener( (v) -> { inCallButtonUiDelegate.setAudioRoute(itemRoute); }); } + + void setAudioState(CallAudioState audioState) { + bluetoothButton.setChecked(audioState.getRoute() == CallAudioState.ROUTE_BLUETOOTH); + speakerButton.setChecked(audioState.getRoute() == CallAudioState.ROUTE_SPEAKER); + headsetButton.setChecked(audioState.getRoute() == CallAudioState.ROUTE_WIRED_HEADSET); + earpieceButton.setChecked(audioState.getRoute() == CallAudioState.ROUTE_EARPIECE); + } } diff --git a/java/com/android/incallui/rtt/impl/RttChatFragment.java b/java/com/android/incallui/rtt/impl/RttChatFragment.java index e56715981..c393393f8 100644 --- a/java/com/android/incallui/rtt/impl/RttChatFragment.java +++ b/java/com/android/incallui/rtt/impl/RttChatFragment.java @@ -107,6 +107,7 @@ public class RttChatFragment extends Fragment private PrimaryCallState primaryCallState = PrimaryCallState.empty(); private boolean isUserScrolling; private boolean shouldAutoScrolling; + private AudioSelectMenu audioSelectMenu; /** * Create a new instance of RttChatFragment. @@ -558,6 +559,9 @@ public class RttChatFragment extends Fragment LogUtil.i("RttChatFragment.setAudioState", "audioState: " + audioState); overflowMenu.setMuteButtonChecked(audioState.isMuted()); overflowMenu.setAudioState(audioState); + if (audioSelectMenu != null) { + audioSelectMenu.setAudioState(audioState); + } } @Override @@ -573,7 +577,7 @@ public class RttChatFragment extends Fragment @Override public void showAudioRouteSelector() { - AudioSelectMenu audioSelectMenu = + audioSelectMenu = new AudioSelectMenu( getContext(), inCallButtonUiDelegate, -- cgit v1.2.3 From 4ef430e759b7e4f07bd1fbc273a7b6937b581b4a Mon Sep 17 00:00:00 2001 From: yueg Date: Fri, 4 May 2018 10:47:35 -0700 Subject: Use Telecom Bluetooth API instead of system Bluetooth API. Bug: 74238896 Test: manual PiperOrigin-RevId: 195437669 Change-Id: I1cb26187b8b90664b72de2a4451283a9fbdc0f10 --- java/com/android/incallui/InCallServiceImpl.java | 3 - .../audiomode/BluetoothDeviceProvider.java | 203 --------------------- .../BluetoothDeviceProviderComponent.java | 39 ---- .../AudioRouteSelectorDialogFragment.java | 43 +++-- java/com/android/incallui/call/TelecomAdapter.java | 11 ++ 5 files changed, 36 insertions(+), 263 deletions(-) delete mode 100644 java/com/android/incallui/audiomode/BluetoothDeviceProvider.java delete mode 100644 java/com/android/incallui/audiomode/BluetoothDeviceProviderComponent.java (limited to 'java/com/android/incallui') diff --git a/java/com/android/incallui/InCallServiceImpl.java b/java/com/android/incallui/InCallServiceImpl.java index d803956e6..b9d0eccba 100644 --- a/java/com/android/incallui/InCallServiceImpl.java +++ b/java/com/android/incallui/InCallServiceImpl.java @@ -26,7 +26,6 @@ import android.telecom.InCallService; import com.android.dialer.blocking.FilteredNumberAsyncQueryHandler; import com.android.dialer.feedback.FeedbackComponent; import com.android.incallui.audiomode.AudioModeProvider; -import com.android.incallui.audiomode.BluetoothDeviceProviderComponent; import com.android.incallui.call.CallList; import com.android.incallui.call.ExternalCallList; import com.android.incallui.call.TelecomAdapter; @@ -98,7 +97,6 @@ public class InCallServiceImpl extends InCallService { final Context context = getApplicationContext(); final ContactInfoCache contactInfoCache = ContactInfoCache.getInstance(context); AudioModeProvider.getInstance().initializeAudioState(this); - BluetoothDeviceProviderComponent.get(context).bluetoothDeviceProvider().setUp(); InCallPresenter.getInstance() .setUp( context, @@ -142,7 +140,6 @@ public class InCallServiceImpl extends InCallService { // Tear down the InCall system InCallPresenter.getInstance().tearDown(); TelecomAdapter.getInstance().clearInCallService(); - BluetoothDeviceProviderComponent.get(this).bluetoothDeviceProvider().tearDown(); if (returnToCallController != null) { returnToCallController.tearDown(); returnToCallController = null; diff --git a/java/com/android/incallui/audiomode/BluetoothDeviceProvider.java b/java/com/android/incallui/audiomode/BluetoothDeviceProvider.java deleted file mode 100644 index 1aa1c20a8..000000000 --- a/java/com/android/incallui/audiomode/BluetoothDeviceProvider.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (C) 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.incallui.audiomode; - -import android.annotation.SuppressLint; -import android.bluetooth.BluetoothAdapter; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothHeadset; -import android.bluetooth.BluetoothProfile; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.util.ArraySet; -import com.android.dialer.common.LogUtil; -import com.android.dialer.inject.ApplicationContext; -import java.lang.reflect.Method; -import java.util.List; -import java.util.Set; -import javax.inject.Inject; -import javax.inject.Singleton; - -/** Proxy class for getting and setting connected/active Bluetooth devices. */ -@Singleton -public final class BluetoothDeviceProvider extends BroadcastReceiver { - - // TODO(yueg): use BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED when possible - private static final String ACTION_ACTIVE_DEVICE_CHANGED = - "android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED"; - - private final Context appContext; - private final BluetoothProfileServiceListener bluetoothProfileServiceListener = - new BluetoothProfileServiceListener(); - - private final Set connectedBluetoothDeviceSet = new ArraySet<>(); - - private BluetoothDevice activeBluetoothDevice; - private BluetoothHeadset bluetoothHeadset; - private boolean isSetUp; - - @Inject - public BluetoothDeviceProvider(@ApplicationContext Context appContext) { - this.appContext = appContext; - } - - public void setUp() { - if (BluetoothAdapter.getDefaultAdapter() == null) { - // Bluetooth is not supported on this hardware platform - return; - } - // Get Bluetooth service including the initial connected device list (should only contain one - // device) - BluetoothAdapter.getDefaultAdapter() - .getProfileProxy(appContext, bluetoothProfileServiceListener, BluetoothProfile.HEADSET); - // Get notified of Bluetooth device update - IntentFilter filter = new IntentFilter(); - filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); - filter.addAction(ACTION_ACTIVE_DEVICE_CHANGED); - appContext.registerReceiver(this, filter); - - isSetUp = true; - } - - public void tearDown() { - if (!isSetUp) { - return; - } - appContext.unregisterReceiver(this); - if (bluetoothHeadset != null) { - BluetoothAdapter.getDefaultAdapter() - .closeProfileProxy(BluetoothProfile.HEADSET, bluetoothHeadset); - } - } - - public Set getConnectedBluetoothDeviceSet() { - return connectedBluetoothDeviceSet; - } - - public BluetoothDevice getActiveBluetoothDevice() { - return activeBluetoothDevice; - } - - @SuppressLint("PrivateApi") - public void setActiveBluetoothDevice(BluetoothDevice bluetoothDevice) { - if (!connectedBluetoothDeviceSet.contains(bluetoothDevice)) { - LogUtil.e("BluetoothProfileServiceListener.setActiveBluetoothDevice", "device is not in set"); - return; - } - // TODO(yueg): use BluetoothHeadset.setActiveDevice() when possible - try { - Method getActiveDeviceMethod = - bluetoothHeadset.getClass().getDeclaredMethod("setActiveDevice", BluetoothDevice.class); - getActiveDeviceMethod.setAccessible(true); - getActiveDeviceMethod.invoke(bluetoothHeadset, bluetoothDevice); - } catch (Exception e) { - LogUtil.e( - "BluetoothProfileServiceListener.setActiveBluetoothDevice", - "failed to call setActiveDevice", - e); - } - } - - @Override - public void onReceive(Context context, Intent intent) { - if (BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED.equals(intent.getAction())) { - handleActionConnectionStateChanged(intent); - } else if (ACTION_ACTIVE_DEVICE_CHANGED.equals(intent.getAction())) { - handleActionActiveDeviceChanged(intent); - } - } - - private void handleActionConnectionStateChanged(Intent intent) { - if (!intent.hasExtra(BluetoothDevice.EXTRA_DEVICE)) { - LogUtil.i( - "BluetoothDeviceProvider.handleActionConnectionStateChanged", - "extra BluetoothDevice.EXTRA_DEVICE not found"); - return; - } - BluetoothDevice bluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - if (bluetoothDevice == null) { - return; - } - - int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1); - if (state == BluetoothProfile.STATE_DISCONNECTED) { - connectedBluetoothDeviceSet.remove(bluetoothDevice); - LogUtil.i("BluetoothDeviceProvider.handleActionConnectionStateChanged", "device removed"); - } else if (state == BluetoothProfile.STATE_CONNECTED) { - connectedBluetoothDeviceSet.add(bluetoothDevice); - LogUtil.i("BluetoothDeviceProvider.handleActionConnectionStateChanged", "device added"); - } - } - - private void handleActionActiveDeviceChanged(Intent intent) { - if (!intent.hasExtra(BluetoothDevice.EXTRA_DEVICE)) { - LogUtil.i( - "BluetoothDeviceProvider.handleActionActiveDeviceChanged", - "extra BluetoothDevice.EXTRA_DEVICE not found"); - return; - } - activeBluetoothDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); - LogUtil.i( - "BluetoothDeviceProvider.handleActionActiveDeviceChanged", - (activeBluetoothDevice == null ? "null" : "")); - } - - private final class BluetoothProfileServiceListener implements BluetoothProfile.ServiceListener { - @Override - @SuppressLint("PrivateApi") - public void onServiceConnected(int profile, BluetoothProfile bluetoothProfile) { - if (profile != BluetoothProfile.HEADSET) { - return; - } - // Get initial connected device list - bluetoothHeadset = (BluetoothHeadset) bluetoothProfile; - List devices = bluetoothProfile.getConnectedDevices(); - for (BluetoothDevice device : devices) { - connectedBluetoothDeviceSet.add(device); - LogUtil.i( - "BluetoothProfileServiceListener.onServiceConnected", "get initial connected device"); - } - - // Get initial active device - // TODO(yueg): use BluetoothHeadset.getActiveDevice() when possible - try { - Method getActiveDeviceMethod = - bluetoothHeadset.getClass().getDeclaredMethod("getActiveDevice"); - getActiveDeviceMethod.setAccessible(true); - activeBluetoothDevice = (BluetoothDevice) getActiveDeviceMethod.invoke(bluetoothHeadset); - LogUtil.i( - "BluetoothProfileServiceListener.onServiceConnected", - "get initial active device" + ((activeBluetoothDevice == null) ? " null" : "")); - } catch (Exception e) { - LogUtil.e( - "BluetoothProfileServiceListener.onServiceConnected", - "failed to call getAcitveDevice", - e); - } - } - - @Override - public void onServiceDisconnected(int profile) { - LogUtil.enterBlock("BluetoothProfileServiceListener.onServiceDisconnected"); - if (profile == BluetoothProfile.HEADSET) { - bluetoothHeadset = null; - } - } - } -} diff --git a/java/com/android/incallui/audiomode/BluetoothDeviceProviderComponent.java b/java/com/android/incallui/audiomode/BluetoothDeviceProviderComponent.java deleted file mode 100644 index 9cd926835..000000000 --- a/java/com/android/incallui/audiomode/BluetoothDeviceProviderComponent.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2017 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.incallui.audiomode; - -import android.content.Context; -import com.android.dialer.inject.HasRootComponent; -import dagger.Subcomponent; - -/** Dagger component for the Bluetooth device provider. */ -@Subcomponent -public abstract class BluetoothDeviceProviderComponent { - - public abstract BluetoothDeviceProvider bluetoothDeviceProvider(); - - public static BluetoothDeviceProviderComponent get(Context context) { - return ((BluetoothDeviceProviderComponent.HasComponent) - ((HasRootComponent) context.getApplicationContext()).component()) - .bluetoothDeviceProviderComponent(); - } - - /** Used to refer to the root application component. */ - public interface HasComponent { - BluetoothDeviceProviderComponent bluetoothDeviceProviderComponent(); - } -} diff --git a/java/com/android/incallui/audioroute/AudioRouteSelectorDialogFragment.java b/java/com/android/incallui/audioroute/AudioRouteSelectorDialogFragment.java index d6946d8de..a561b5ee5 100644 --- a/java/com/android/incallui/audioroute/AudioRouteSelectorDialogFragment.java +++ b/java/com/android/incallui/audioroute/AudioRouteSelectorDialogFragment.java @@ -39,12 +39,12 @@ import com.android.dialer.common.FragmentUtils; import com.android.dialer.common.LogUtil; import com.android.dialer.logging.DialerImpression; import com.android.dialer.logging.Logger; -import com.android.incallui.audiomode.BluetoothDeviceProviderComponent; import com.android.incallui.call.CallList; import com.android.incallui.call.DialerCall; +import com.android.incallui.call.TelecomAdapter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.Set; +import java.util.Collection; /** Shows picker for audio routes */ public class AudioRouteSelectorDialogFragment extends BottomSheetDialogFragment { @@ -91,24 +91,33 @@ public class AudioRouteSelectorDialogFragment extends BottomSheetDialogFragment @Nullable @Override + @SuppressLint("NewApi") public View onCreateView( LayoutInflater layoutInflater, @Nullable ViewGroup viewGroup, @Nullable Bundle bundle) { View view = layoutInflater.inflate(R.layout.audioroute_selector, viewGroup, false); CallAudioState audioState = getArguments().getParcelable(ARG_AUDIO_STATE); - Set bluetoothDeviceSet = - BluetoothDeviceProviderComponent.get(getContext()) - .bluetoothDeviceProvider() - .getConnectedBluetoothDeviceSet(); - for (BluetoothDevice device : bluetoothDeviceSet) { - boolean selected = - (audioState.getRoute() == CallAudioState.ROUTE_BLUETOOTH) - && (bluetoothDeviceSet.size() == 1 - || device.equals( - BluetoothDeviceProviderComponent.get(getContext()) - .bluetoothDeviceProvider() - .getActiveBluetoothDevice())); - TextView textView = createBluetoothItem(device, selected); + if (BuildCompat.isAtLeastP()) { + // Create items for all connected Bluetooth devices + Collection bluetoothDeviceSet = audioState.getSupportedBluetoothDevices(); + for (BluetoothDevice device : bluetoothDeviceSet) { + boolean selected = + (audioState.getRoute() == CallAudioState.ROUTE_BLUETOOTH) + && (bluetoothDeviceSet.size() == 1 + || device.equals(audioState.getActiveBluetoothDevice())); + TextView textView = createBluetoothItem(device, selected); + ((LinearLayout) view).addView(textView, 0); + } + } else { + // Only create Bluetooth audio route + TextView textView = + (TextView) getLayoutInflater().inflate(R.layout.audioroute_item, null, false); + textView.setText(getString(R.string.audioroute_bluetooth)); + initItem( + textView, + CallAudioState.ROUTE_BLUETOOTH, + audioState, + DialerImpression.Type.IN_CALL_SWITCH_AUDIO_ROUTE_BLUETOOTH); ((LinearLayout) view).addView(textView, 0); } @@ -183,9 +192,7 @@ public class AudioRouteSelectorDialogFragment extends BottomSheetDialogFragment AudioRouteSelectorDialogFragment.this, AudioRouteSelectorPresenter.class) .onAudioRouteSelected(CallAudioState.ROUTE_BLUETOOTH); // Set active Bluetooth device - BluetoothDeviceProviderComponent.get(getContext()) - .bluetoothDeviceProvider() - .setActiveBluetoothDevice(bluetoothDevice); + TelecomAdapter.getInstance().requestBluetoothAudio(bluetoothDevice); dismiss(); }); diff --git a/java/com/android/incallui/call/TelecomAdapter.java b/java/com/android/incallui/call/TelecomAdapter.java index a7e10d37c..4ae1bc1db 100644 --- a/java/com/android/incallui/call/TelecomAdapter.java +++ b/java/com/android/incallui/call/TelecomAdapter.java @@ -16,7 +16,9 @@ package com.android.incallui.call; +import android.annotation.TargetApi; import android.app.Notification; +import android.bluetooth.BluetoothDevice; import android.content.ActivityNotFoundException; import android.content.Intent; import android.os.Looper; @@ -193,4 +195,13 @@ public class TelecomAdapter implements InCallServiceListener { "no inCallService available for stopping foreground notification"); } } + + @TargetApi(28) + public void requestBluetoothAudio(BluetoothDevice bluetoothDevice) { + if (inCallService != null) { + inCallService.requestBluetoothAudio(bluetoothDevice); + } else { + LogUtil.e("TelecomAdapter.requestBluetoothAudio", "inCallService is null"); + } + } } -- cgit v1.2.3