diff options
author | Santos Cordon <santoscordon@google.com> | 2013-08-06 19:06:19 -0700 |
---|---|---|
committer | Santos Cordon <santoscordon@google.com> | 2013-08-07 18:08:12 -0700 |
commit | ee4a3feabd047f27540c5967b88d407855f3de8a (patch) | |
tree | 5c60d1fc13b4d69006ca7ff2c456f999e00f8f78 /InCallUI | |
parent | 6d231f52e4f00399330d772f2a337283803f3a9d (diff) |
Audio Routing support in UI
Changes:
- AudioModeProvider - receives audio mode changes from
CallHandlerService
- CallButtonPresenter listens to AudioModeProvider so that it can use
those changes in the UI.
- CallButtonFragment uses the audio mode from Presenter() to display:
- The correct layers for the supported modes
- The popup menu when bluetooth is enabled
Change-Id: I8ec4024f7bbb5e3b1cfdaae20a4dd2f85ae802cf
Diffstat (limited to 'InCallUI')
18 files changed, 513 insertions, 46 deletions
diff --git a/InCallUI/res/drawable-hdpi/ic_ab_dialer_holo_dark.png b/InCallUI/res/drawable-hdpi/ic_ab_dialer_holo_dark.png Binary files differnew file mode 100644 index 000000000..ecfeb2db8 --- /dev/null +++ b/InCallUI/res/drawable-hdpi/ic_ab_dialer_holo_dark.png diff --git a/InCallUI/res/drawable-hdpi/ic_bluetooth_holo_dark.png b/InCallUI/res/drawable-hdpi/ic_bluetooth_holo_dark.png Binary files differnew file mode 100644 index 000000000..ba22b0f8d --- /dev/null +++ b/InCallUI/res/drawable-hdpi/ic_bluetooth_holo_dark.png diff --git a/InCallUI/res/drawable-ldrtl-hdpi/ic_ab_dialer_holo_dark.png b/InCallUI/res/drawable-ldrtl-hdpi/ic_ab_dialer_holo_dark.png Binary files differnew file mode 100644 index 000000000..7ec3709bb --- /dev/null +++ b/InCallUI/res/drawable-ldrtl-hdpi/ic_ab_dialer_holo_dark.png diff --git a/InCallUI/res/drawable-ldrtl-mdpi/ic_ab_dialer_holo_dark.png b/InCallUI/res/drawable-ldrtl-mdpi/ic_ab_dialer_holo_dark.png Binary files differnew file mode 100644 index 000000000..6020b3de1 --- /dev/null +++ b/InCallUI/res/drawable-ldrtl-mdpi/ic_ab_dialer_holo_dark.png diff --git a/InCallUI/res/drawable-ldrtl-xhdpi/ic_ab_dialer_holo_dark.png b/InCallUI/res/drawable-ldrtl-xhdpi/ic_ab_dialer_holo_dark.png Binary files differnew file mode 100644 index 000000000..c42e7e391 --- /dev/null +++ b/InCallUI/res/drawable-ldrtl-xhdpi/ic_ab_dialer_holo_dark.png diff --git a/InCallUI/res/drawable-mdpi/ic_ab_dialer_holo_dark.png b/InCallUI/res/drawable-mdpi/ic_ab_dialer_holo_dark.png Binary files differnew file mode 100644 index 000000000..51ad9e375 --- /dev/null +++ b/InCallUI/res/drawable-mdpi/ic_ab_dialer_holo_dark.png diff --git a/InCallUI/res/drawable-mdpi/ic_bluetooth_holo_dark.png b/InCallUI/res/drawable-mdpi/ic_bluetooth_holo_dark.png Binary files differnew file mode 100644 index 000000000..fb69031dc --- /dev/null +++ b/InCallUI/res/drawable-mdpi/ic_bluetooth_holo_dark.png diff --git a/InCallUI/res/drawable-xhdpi/ic_ab_dialer_holo_dark.png b/InCallUI/res/drawable-xhdpi/ic_ab_dialer_holo_dark.png Binary files differnew file mode 100644 index 000000000..3f43a8221 --- /dev/null +++ b/InCallUI/res/drawable-xhdpi/ic_ab_dialer_holo_dark.png diff --git a/InCallUI/res/drawable-xhdpi/ic_bluetooth_holo_dark.png b/InCallUI/res/drawable-xhdpi/ic_bluetooth_holo_dark.png Binary files differnew file mode 100644 index 000000000..24cb8939d --- /dev/null +++ b/InCallUI/res/drawable-xhdpi/ic_bluetooth_holo_dark.png diff --git a/InCallUI/res/menu/incall_audio_mode_menu.xml b/InCallUI/res/menu/incall_audio_mode_menu.xml new file mode 100644 index 000000000..013989500 --- /dev/null +++ b/InCallUI/res/menu/incall_audio_mode_menu.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2011 Google Inc. + + 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. +--> + +<!-- "Audio mode" popup menu for the in-call UI. --> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- TODO: Need final icon assets. Also, PopupMenu currently ignores the + android:icon attribute anyway(!) --> + <item android:id="@+id/audio_mode_speaker" + android:icon="@drawable/ic_sound_holo_dark" + android:title="@string/audio_mode_speaker" /> + + <!-- We display *either* "earpiece" or "wired headset", never both, + depending on whether a wired headset is physically plugged in + (see InCallTouchUi.showAudioModePopup().) --> + <item android:id="@+id/audio_mode_earpiece" + android:icon="@drawable/ic_ab_dialer_holo_dark" + android:title="@string/audio_mode_earpiece" /> + <item android:id="@+id/audio_mode_wired_headset" + android:icon="@drawable/ic_ab_dialer_holo_dark" + android:title="@string/audio_mode_wired_headset" /> + + <item android:id="@+id/audio_mode_bluetooth" + android:icon="@drawable/ic_bluetooth_holo_dark" + android:title="@string/audio_mode_bluetooth" /> +</menu> diff --git a/InCallUI/src/com/android/incallui/AnswerFragment.java b/InCallUI/src/com/android/incallui/AnswerFragment.java index 196a66bb0..79247c56c 100644 --- a/InCallUI/src/com/android/incallui/AnswerFragment.java +++ b/InCallUI/src/com/android/incallui/AnswerFragment.java @@ -69,6 +69,11 @@ public class AnswerFragment extends BaseFragment<AnswerPresenter> implements } @Override + public void onDestroyView() { + getPresenter().onUiUnready(this); + } + + @Override public void showAnswerUi(boolean show) { getView().setVisibility(show ? View.VISIBLE : View.GONE); } diff --git a/InCallUI/src/com/android/incallui/AudioModeProvider.java b/InCallUI/src/com/android/incallui/AudioModeProvider.java new file mode 100644 index 000000000..abbbfb6e6 --- /dev/null +++ b/InCallUI/src/com/android/incallui/AudioModeProvider.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2013 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; + +import com.google.android.collect.Lists; + +import com.android.services.telephony.common.AudioMode; + +import java.util.List; + + +/** + * Proxy class for getting and setting the audio mode. + */ +/* package */ class AudioModeProvider { + + private static AudioModeProvider sAudioModeProvider; + private int mAudioMode = AudioMode.EARPIECE; + private int mSupportedModes = AudioMode.ALL_MODES; + private final List<AudioModeListener> mListeners = Lists.newArrayList(); + + public static synchronized AudioModeProvider getInstance() { + if (sAudioModeProvider == null) { + sAudioModeProvider = new AudioModeProvider(); + } + return sAudioModeProvider; + } + + /** + * Access only through getInstance() + */ + private AudioModeProvider() { + } + + public void onAudioModeChange(int newMode) { + mAudioMode = newMode; + + for (AudioModeListener l : mListeners) { + l.onAudioMode(mAudioMode); + } + } + + public void onSupportedAudioModeChange(int newModeMask) { + mSupportedModes = newModeMask; + + for (AudioModeListener l : mListeners) { + l.onSupportedAudioMode(mSupportedModes); + } + } + + public void addListener(AudioModeListener listener) { + if (!mListeners.contains(listener)) { + mListeners.add(listener); + } + } + + public void removeListener(AudioModeListener listener) { + if (mListeners.contains(listener)) { + mListeners.remove(listener); + } + } + + public int getSupportedModes() { + return mSupportedModes; + } + + public int getAudioMode() { + return mAudioMode; + } + + /* package */ interface AudioModeListener { + void onAudioMode(int newMode); + void onSupportedAudioMode(int modeMask); + } +} diff --git a/InCallUI/src/com/android/incallui/CallButtonFragment.java b/InCallUI/src/com/android/incallui/CallButtonFragment.java index 772251dfa..175fe4d95 100644 --- a/InCallUI/src/com/android/incallui/CallButtonFragment.java +++ b/InCallUI/src/com/android/incallui/CallButtonFragment.java @@ -17,39 +17,48 @@ package com.android.incallui; import android.content.Context; +import android.graphics.drawable.LayerDrawable; import android.media.AudioManager; import android.os.Bundle; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.CompoundButton; +import android.widget.PopupMenu; +import android.widget.PopupMenu.OnDismissListener; +import android.widget.PopupMenu.OnMenuItemClickListener; import android.widget.ToggleButton; +import com.android.services.telephony.common.AudioMode; + /** * Fragment for call control buttons */ public class CallButtonFragment extends BaseFragment<CallButtonPresenter> - implements CallButtonPresenter.CallButtonUi { + implements CallButtonPresenter.CallButtonUi, OnMenuItemClickListener, + OnDismissListener { private ToggleButton mMuteButton; private ToggleButton mAudioButton; private ToggleButton mHoldButton; private ToggleButton mShowDialpadButton; + private PopupMenu mAudioModePopup; + private boolean mAudioModePopupVisible; private View mEndCallButton; @Override CallButtonPresenter createPresenter() { - return new CallButtonPresenter(); + // TODO: find a cleaner way to include audio mode provider than + // having a singleton instance. + return new CallButtonPresenter(AudioModeProvider.getInstance()); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - final AudioManager audioManager = (AudioManager) getActivity().getSystemService( - Context.AUDIO_SERVICE); - getPresenter().init(audioManager); } @Override @@ -74,10 +83,10 @@ public class CallButtonFragment extends BaseFragment<CallButtonPresenter> }); mAudioButton = (ToggleButton) parent.findViewById(R.id.audioButton); - mAudioButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + mAudioButton.setOnClickListener(new View.OnClickListener() { @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - getPresenter().speakerClicked(isChecked); + public void onClick(View view) { + onAudioButtonClicked(); } }); @@ -103,6 +112,14 @@ public class CallButtonFragment extends BaseFragment<CallButtonPresenter> @Override public void onViewCreated(View view, Bundle savedInstanceState) { getPresenter().onUiReady(this); + + // set the buttons + updateAudioButtons(getPresenter().getSupportedAudio()); + } + + @Override + public void onDestroyView() { + getPresenter().onUiUnready(this); } @Override @@ -119,17 +136,256 @@ public class CallButtonFragment extends BaseFragment<CallButtonPresenter> mMuteButton.setChecked(value); } - /** - * TODO(klp): Rename this from setSpeaker() to setAudio() once it does more than speakerphone. - */ @Override - public void setSpeaker(boolean value) { - mAudioButton.setChecked(value); + public void setHold(boolean value) { + mHoldButton.setChecked(value); } @Override - public void setHold(boolean value) { - mHoldButton.setChecked(value); + public void setAudio(int mode) { + } + + @Override + public void setSupportedAudio(int modeMask) { + updateAudioButtons(modeMask); + refreshAudioModePopup(); + } + + @Override + public boolean onMenuItemClick(MenuItem item) { + Logger.d(this, "- onMenuItemClick: " + item); + Logger.d(this, " id: " + item.getItemId()); + Logger.d(this, " title: '" + item.getTitle() + "'"); + + int mode = AudioMode.WIRED_OR_EARPIECE; + + switch (item.getItemId()) { + case R.id.audio_mode_speaker: + mode = AudioMode.SPEAKER; + break; + case R.id.audio_mode_earpiece: + case R.id.audio_mode_wired_headset: + // InCallAudioMode.EARPIECE means either the handset earpiece, + // or the wired headset (if connected.) + mode = AudioMode.WIRED_OR_EARPIECE; + break; + case R.id.audio_mode_bluetooth: + mode = AudioMode.BLUETOOTH; + break; + default: + Logger.e(this, "onMenuItemClick: unexpected View ID " + item.getItemId() + + " (MenuItem = '" + item + "')"); + break; + } + + getPresenter().setAudioMode(mode); + + return true; + } + + // PopupMenu.OnDismissListener implementation; see showAudioModePopup(). + // This gets called when the PopupMenu gets dismissed for *any* reason, like + // the user tapping outside its bounds, or pressing Back, or selecting one + // of the menu items. + @Override + public void onDismiss(PopupMenu menu) { + Logger.d(this, "- onDismiss: " + menu); + mAudioModePopupVisible = false; + } + + /** + * Checks for supporting modes. If bluetooth is supported, it uses the audio + * pop up menu. Otherwise, it toggles the speakerphone. + */ + private void onAudioButtonClicked() { + Logger.d(this, "onAudioButtonClicked: " + + AudioMode.toString(getPresenter().getSupportedAudio())); + + if (isSupported(AudioMode.BLUETOOTH)) { + showAudioModePopup(); + } else { + getPresenter().toggleSpeakerphone(); + } + } + + /** + * Refreshes the "Audio mode" popup if it's visible. This is useful + * (for example) when a wired headset is plugged or unplugged, + * since we need to switch back and forth between the "earpiece" + * and "wired headset" items. + * + * This is safe to call even if the popup is already dismissed, or even if + * you never called showAudioModePopup() in the first place. + */ + public void refreshAudioModePopup() { + if (mAudioModePopup != null && mAudioModePopupVisible) { + // Dismiss the previous one + mAudioModePopup.dismiss(); // safe even if already dismissed + // And bring up a fresh PopupMenu + showAudioModePopup(); + } + } + + /** + * Updates the audio button so that the appriopriate visual layers + * are visible based on the supported audio formats. + */ + private void updateAudioButtons(int supportedModes) { + final boolean bluetoothSupported = isSupported(AudioMode.BLUETOOTH); + final boolean speakerSupported = isSupported(AudioMode.SPEAKER); + + boolean audioButtonEnabled = false; + boolean audioButtonChecked = false; + boolean showMoreIndicator = false; + + boolean showBluetoothIcon = false; + boolean showSpeakerphoneOnIcon = false; + boolean showSpeakerphoneOffIcon = false; + boolean showHandsetIcon = false; + + boolean showToggleIndicator = false; + + if (bluetoothSupported) { + Logger.d(this, "updateAudioButtons - popup menu mode"); + + audioButtonEnabled = true; + showMoreIndicator = true; + // The audio button is NOT a toggle in this state. (And its + // setChecked() state is irrelevant since we completely hide the + // btn_compound_background layer anyway.) + + // Update desired layers: + if (isAudio(AudioMode.BLUETOOTH)) { + showBluetoothIcon = true; + } else if (isAudio(AudioMode.SPEAKER)) { + showSpeakerphoneOnIcon = true; + } else { + showHandsetIcon = true; + // TODO: if a wired headset is plugged in, that takes precedence + // over the handset earpiece. If so, maybe we should show some + // sort of "wired headset" icon here instead of the "handset + // earpiece" icon. (Still need an asset for that, though.) + } + } else if (speakerSupported) { + Logger.d(this, "updateAudioButtons - speaker toggle mode"); + + audioButtonEnabled = true; + + // The audio button *is* a toggle in this state, and indicated the + // current state of the speakerphone. + audioButtonChecked = isAudio(AudioMode.SPEAKER); + + // update desired layers: + showToggleIndicator = true; + + showSpeakerphoneOnIcon = isAudio(AudioMode.SPEAKER); + showSpeakerphoneOffIcon = !showSpeakerphoneOnIcon; + } else { + Logger.d(this, "updateAudioButtons - disabled..."); + + // The audio button is a toggle in this state, but that's mostly + // irrelevant since it's always disabled and unchecked. + audioButtonEnabled = false; + audioButtonChecked = false; + + // update desired layers: + showToggleIndicator = true; + showSpeakerphoneOffIcon = true; + } + + // Finally, update it all! + + Logger.v(this, "audioButtonEnabled: " + audioButtonEnabled); + Logger.v(this, "audioButtonChecked: " + audioButtonChecked); + Logger.v(this, "showMoreIndicator: " + showMoreIndicator); + Logger.v(this, "showBluetoothIcon: " + showBluetoothIcon); + Logger.v(this, "showSpeakerphoneOnIcon: " + showSpeakerphoneOnIcon); + Logger.v(this, "showSpeakerphoneOffIcon: " + showSpeakerphoneOffIcon); + Logger.v(this, "showHandsetIcon: " + showHandsetIcon); + + // Constants for Drawable.setAlpha() + final int HIDDEN = 0; + final int VISIBLE = 255; + + mAudioButton.setEnabled(audioButtonEnabled); + mAudioButton.setChecked(audioButtonChecked); + + final LayerDrawable layers = (LayerDrawable) mAudioButton.getBackground(); + Logger.d(this, "'layers' drawable: " + layers); + + layers.findDrawableByLayerId(R.id.compoundBackgroundItem) + .setAlpha(showToggleIndicator ? VISIBLE : HIDDEN); + + layers.findDrawableByLayerId(R.id.moreIndicatorItem) + .setAlpha(showMoreIndicator ? VISIBLE : HIDDEN); + + layers.findDrawableByLayerId(R.id.bluetoothItem) + .setAlpha(showBluetoothIcon ? VISIBLE : HIDDEN); + + layers.findDrawableByLayerId(R.id.handsetItem) + .setAlpha(showHandsetIcon ? VISIBLE : HIDDEN); + + layers.findDrawableByLayerId(R.id.speakerphoneOnItem) + .setAlpha(showSpeakerphoneOnIcon ? VISIBLE : HIDDEN); + + layers.findDrawableByLayerId(R.id.speakerphoneOffItem) + .setAlpha(showSpeakerphoneOffIcon ? VISIBLE : HIDDEN); + } + + private void showAudioModePopup() { + Logger.d(this, "showAudioPopup()..."); + + mAudioModePopup = new PopupMenu(getView().getContext(), mAudioButton /* anchorView */); + mAudioModePopup.getMenuInflater().inflate(R.menu.incall_audio_mode_menu, + mAudioModePopup.getMenu()); + mAudioModePopup.setOnMenuItemClickListener(this); + mAudioModePopup.setOnDismissListener(this); + + final Menu menu = mAudioModePopup.getMenu(); + + // TODO: Still need to have the "currently active" audio mode come + // up pre-selected (or focused?) with a blue highlight. Still + // need exact visual design, and possibly framework support for this. + // See comments below for the exact logic. + + final MenuItem speakerItem = menu.findItem(R.id.audio_mode_speaker); + speakerItem.setEnabled(isSupported(AudioMode.SPEAKER)); + // TODO: Show speakerItem as initially "selected" if + // speaker is on. + + // We display *either* "earpiece" or "wired headset", never both, + // depending on whether a wired headset is physically plugged in. + final MenuItem earpieceItem = menu.findItem(R.id.audio_mode_earpiece); + final MenuItem wiredHeadsetItem = menu.findItem(R.id.audio_mode_wired_headset); + + final boolean usingHeadset = isSupported(AudioMode.WIRED_HEADSET); + earpieceItem.setVisible(!usingHeadset); + earpieceItem.setEnabled(!usingHeadset); + wiredHeadsetItem.setVisible(usingHeadset); + wiredHeadsetItem.setEnabled(usingHeadset); + // TODO: Show the above item (either earpieceItem or wiredHeadsetItem) + // as initially "selected" if speakerOn and + // bluetoothIndicatorOn are both false. + + final MenuItem bluetoothItem = menu.findItem(R.id.audio_mode_bluetooth); + bluetoothItem.setEnabled(isSupported(AudioMode.BLUETOOTH)); + // TODO: Show bluetoothItem as initially "selected" if + // bluetoothIndicatorOn is true. + + mAudioModePopup.show(); + + // Unfortunately we need to manually keep track of the popup menu's + // visiblity, since PopupMenu doesn't have an isShowing() method like + // Dialogs do. + mAudioModePopupVisible = true; + } + + private boolean isSupported(int mode) { + return (mode == (getPresenter().getSupportedAudio() & mode)); + } + + private boolean isAudio(int mode) { + return (mode == getPresenter().getAudioMode()); } @Override diff --git a/InCallUI/src/com/android/incallui/CallButtonPresenter.java b/InCallUI/src/com/android/incallui/CallButtonPresenter.java index 35e6ab7f6..d4b2cf2de 100644 --- a/InCallUI/src/com/android/incallui/CallButtonPresenter.java +++ b/InCallUI/src/com/android/incallui/CallButtonPresenter.java @@ -20,28 +20,38 @@ import com.google.common.base.Preconditions; import android.media.AudioManager; +import com.android.incallui.AudioModeProvider.AudioModeListener; import com.android.incallui.InCallPresenter.InCallState; import com.android.incallui.InCallPresenter.InCallStateListener; +import com.android.services.telephony.common.AudioMode; import com.android.services.telephony.common.Call; /** * Logic for call buttons. */ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButtonUi> - implements InCallStateListener { + implements InCallStateListener, AudioModeListener { - private AudioManager mAudioManager; private Call mCall; + private final AudioModeProvider mAudioModeProvider; - public void init(AudioManager audioManager) { - mAudioManager = audioManager; + public CallButtonPresenter(AudioModeProvider audioModeProvider) { + + // AudioModeProvider works effectively as a pass through. However, if we + // had this presenter listen for changes directly, it would have to live forever + // or risk missing important updates. + mAudioModeProvider = audioModeProvider; + mAudioModeProvider.addListener(this); } @Override public void onUiReady(CallButtonUi ui) { super.onUiReady(ui); - getUi().setMute(mAudioManager.isMicrophoneMute()); - getUi().setSpeaker(mAudioManager.isSpeakerphoneOn()); + } + + @Override + public void onUiUnready(CallButtonUi ui) { + mAudioModeProvider.removeListener(this); } @Override @@ -55,23 +65,62 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto } } + @Override + public void onAudioMode(int mode) { + getUi().setAudio(mode); + } + + @Override + public void onSupportedAudioMode(int mask) { + getUi().setSupportedAudio(mask); + } + + public int getAudioMode() { + return mAudioModeProvider.getAudioMode(); + } + + public int getSupportedAudio() { + return mAudioModeProvider.getSupportedModes(); + } + + public void setAudioMode(int mode) { + + // TODO: Set a intermediate state in this presenter until we get + // an update for onAudioMode(). This will make UI response immediate + // if it turns out to be slow + + Logger.d(this, "Sending new Audio Mode: " + AudioMode.toString(mode)); + CallCommandClient.getInstance().setAudioMode(mode); + } + + /** + * Function assumes that bluetooth is not supported. + */ + public void toggleSpeakerphone() { + // this function should not be called if bluetooth is available + if (0 != (AudioMode.BLUETOOTH & mAudioModeProvider.getSupportedModes())) { + + // It's clear the UI is off, so update the supported mode once again. + Logger.e(this, "toggling speakerphone not allowed when bluetooth supported."); + getUi().setSupportedAudio(mAudioModeProvider.getSupportedModes()); + return; + } + + int newMode = AudioMode.SPEAKER; + + // if speakerphone is already on, change to wired/earpiece + if (mAudioModeProvider.getAudioMode() == AudioMode.SPEAKER) { + newMode = AudioMode.WIRED_OR_EARPIECE; + } + + setAudioMode(newMode); + } + public void endCallClicked() { Preconditions.checkNotNull(mCall); // TODO(klp): hook up call id. CallCommandClient.getInstance().disconnectCall(mCall.getCallId()); - - // TODO(klp): Remove once all state is gathered from CallList. - // This will be wrong when you disconnect from a call if - // the user has another call on hold. - reset(); - } - - private void reset() { - getUi().setVisible(false); - getUi().setMute(false); - getUi().setSpeaker(false); - getUi().setHold(false); } public void muteClicked(boolean checked) { @@ -81,13 +130,6 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto getUi().setMute(checked); } - public void speakerClicked(boolean checked) { - Logger.d(this, "turning on speaker: " + checked); - - CallCommandClient.getInstance().turnSpeakerOn(checked); - getUi().setSpeaker(checked); - } - public void holdClicked(boolean checked) { Preconditions.checkNotNull(mCall); @@ -106,8 +148,9 @@ public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButto public interface CallButtonUi extends Ui { void setVisible(boolean on); void setMute(boolean on); - void setSpeaker(boolean on); void setHold(boolean on); void displayDialpad(boolean on); + void setAudio(int mode); + void setSupportedAudio(int mask); } } diff --git a/InCallUI/src/com/android/incallui/CallCardFragment.java b/InCallUI/src/com/android/incallui/CallCardFragment.java index b584d5e86..d4782c5fe 100644 --- a/InCallUI/src/com/android/incallui/CallCardFragment.java +++ b/InCallUI/src/com/android/incallui/CallCardFragment.java @@ -55,6 +55,7 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter> return inflater.inflate(R.layout.call_card, container, false); } + @Override public void onViewCreated(View view, Bundle savedInstanceState) { mPhoneNumber = (TextView) view.findViewById(R.id.phoneNumber); @@ -69,6 +70,11 @@ public class CallCardFragment extends BaseFragment<CallCardPresenter> } @Override + public void onDestroyView() { + getPresenter().onUiUnready(this); + } + + @Override public void setVisible(boolean on) { if (on) { getView().setVisibility(View.VISIBLE); diff --git a/InCallUI/src/com/android/incallui/CallCommandClient.java b/InCallUI/src/com/android/incallui/CallCommandClient.java index 80a9e9e10..8e0a16c46 100644 --- a/InCallUI/src/com/android/incallui/CallCommandClient.java +++ b/InCallUI/src/com/android/incallui/CallCommandClient.java @@ -88,9 +88,9 @@ public class CallCommandClient { } } - public void turnSpeakerOn(boolean onOff) { + public void setAudioMode(int mode) { try { - mCommandService.speaker(onOff); + mCommandService.setAudioMode(mode); } catch (RemoteException e) { Logger.e(this, "Error setting speaker.", e); } diff --git a/InCallUI/src/com/android/incallui/CallHandlerService.java b/InCallUI/src/com/android/incallui/CallHandlerService.java index 173f6a345..8431fc91e 100644 --- a/InCallUI/src/com/android/incallui/CallHandlerService.java +++ b/InCallUI/src/com/android/incallui/CallHandlerService.java @@ -22,6 +22,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Message; +import com.android.services.telephony.common.AudioMode; import com.android.services.telephony.common.Call; import com.android.services.telephony.common.ICallCommandService; import com.android.services.telephony.common.ICallHandlerService; @@ -39,10 +40,14 @@ public class CallHandlerService extends Service { private static final int ON_UPDATE_CALL = 1; private static final int ON_UPDATE_MULTI_CALL = 2; private static final int ON_UPDATE_CALL_WITH_TEXT_RESPONSES = 3; + private static final int ON_AUDIO_MODE = 4; + private static final int ON_SUPPORTED_AUDIO_MODE = 5; + private CallList mCallList; private Handler mMainHandler; private InCallPresenter mInCallPresenter; + private AudioModeProvider mAudioModeProvider; @Override public void onCreate() { @@ -51,6 +56,7 @@ public class CallHandlerService extends Service { mCallList = CallList.getInstance(); mMainHandler = new MainHandler(); mInCallPresenter = InCallPresenter.init(this); + mAudioModeProvider = AudioModeProvider.getInstance(); } @Override @@ -66,12 +72,13 @@ public class CallHandlerService extends Service { @Override public void setCallCommandService(ICallCommandService service) { - Logger.d(this, "onConnected: " + service.toString()); + Logger.d(CallHandlerService.this, "onConnected: " + service.toString()); CallCommandClient.init(service); } @Override public void onDisconnect(Call call) { + Logger.d(CallHandlerService.this, "onDisconnected"); mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_UPDATE_CALL, 0, 0, call)); } @@ -86,16 +93,24 @@ public class CallHandlerService extends Service { @Override public void onUpdate(List<Call> calls, boolean fullUpdate) { + Logger.d(CallHandlerService.this, "onUpdate "); // TODO(klp): Add use of fullUpdate to message mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_UPDATE_MULTI_CALL, 0, 0, calls)); } @Override public void onAudioModeChange(int mode) { + Logger.d(CallHandlerService.this, "onAudioModeChange : " + AudioMode.toString(mode)); + mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_AUDIO_MODE, mode, 0, null)); } @Override - public void onAudioModeSupportChange(int modeMask) { + public void onSupportedAudioModeChange(int modeMask) { + Logger.d(CallHandlerService.this, "onSupportedAudioModeChange : " + + AudioMode.toString(modeMask)); + + mMainHandler.sendMessage( + mMainHandler.obtainMessage(ON_SUPPORTED_AUDIO_MODE, modeMask, 0, null)); } }; @@ -115,6 +130,8 @@ public class CallHandlerService extends Service { } private void executeMessage(Message msg) { + Logger.d(this, "executeMessage " + msg.what); + switch (msg.what) { case ON_UPDATE_CALL: mCallList.onUpdate((Call) msg.obj); @@ -125,6 +142,12 @@ public class CallHandlerService extends Service { case ON_UPDATE_CALL_WITH_TEXT_RESPONSES: mCallList.onUpdate((AbstractMap.SimpleEntry<Call, List<String> >) msg.obj); break; + case ON_AUDIO_MODE: + mAudioModeProvider.onAudioModeChange(msg.arg1); + break; + case ON_SUPPORTED_AUDIO_MODE: + mAudioModeProvider.onSupportedAudioModeChange(msg.arg1); + break; default: break; } diff --git a/InCallUI/src/com/android/incallui/Presenter.java b/InCallUI/src/com/android/incallui/Presenter.java index d4024d53f..b4962aea6 100644 --- a/InCallUI/src/com/android/incallui/Presenter.java +++ b/InCallUI/src/com/android/incallui/Presenter.java @@ -32,6 +32,13 @@ public abstract class Presenter<U extends Ui> { mUi = ui; } + /** + * Called when the UI view is destroyed in Fragment.onDestroyView(). + */ + public void onUiUnready(U ui) { + mUi = null; + } + public U getUi() { return mUi; } |