summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSantos Cordon <santoscordon@google.com>2013-08-06 19:06:19 -0700
committerSantos Cordon <santoscordon@google.com>2013-08-07 18:08:12 -0700
commitee4a3feabd047f27540c5967b88d407855f3de8a (patch)
tree5c60d1fc13b4d69006ca7ff2c456f999e00f8f78
parent6d231f52e4f00399330d772f2a337283803f3a9d (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
-rw-r--r--InCallUI/res/drawable-hdpi/ic_ab_dialer_holo_dark.pngbin0 -> 1487 bytes
-rw-r--r--InCallUI/res/drawable-hdpi/ic_bluetooth_holo_dark.pngbin0 -> 1852 bytes
-rw-r--r--InCallUI/res/drawable-ldrtl-hdpi/ic_ab_dialer_holo_dark.pngbin0 -> 5665 bytes
-rw-r--r--InCallUI/res/drawable-ldrtl-mdpi/ic_ab_dialer_holo_dark.pngbin0 -> 5014 bytes
-rw-r--r--InCallUI/res/drawable-ldrtl-xhdpi/ic_ab_dialer_holo_dark.pngbin0 -> 6513 bytes
-rw-r--r--InCallUI/res/drawable-mdpi/ic_ab_dialer_holo_dark.pngbin0 -> 1019 bytes
-rw-r--r--InCallUI/res/drawable-mdpi/ic_bluetooth_holo_dark.pngbin0 -> 1228 bytes
-rw-r--r--InCallUI/res/drawable-xhdpi/ic_ab_dialer_holo_dark.pngbin0 -> 2116 bytes
-rw-r--r--InCallUI/res/drawable-xhdpi/ic_bluetooth_holo_dark.pngbin0 -> 2558 bytes
-rw-r--r--InCallUI/res/menu/incall_audio_mode_menu.xml38
-rw-r--r--InCallUI/src/com/android/incallui/AnswerFragment.java5
-rw-r--r--InCallUI/src/com/android/incallui/AudioModeProvider.java89
-rw-r--r--InCallUI/src/com/android/incallui/CallButtonFragment.java288
-rw-r--r--InCallUI/src/com/android/incallui/CallButtonPresenter.java95
-rw-r--r--InCallUI/src/com/android/incallui/CallCardFragment.java6
-rw-r--r--InCallUI/src/com/android/incallui/CallCommandClient.java4
-rw-r--r--InCallUI/src/com/android/incallui/CallHandlerService.java27
-rw-r--r--InCallUI/src/com/android/incallui/Presenter.java7
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
new file mode 100644
index 000000000..ecfeb2db8
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/ic_ab_dialer_holo_dark.png
Binary files differ
diff --git a/InCallUI/res/drawable-hdpi/ic_bluetooth_holo_dark.png b/InCallUI/res/drawable-hdpi/ic_bluetooth_holo_dark.png
new file mode 100644
index 000000000..ba22b0f8d
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/ic_bluetooth_holo_dark.png
Binary files differ
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
new file mode 100644
index 000000000..7ec3709bb
--- /dev/null
+++ b/InCallUI/res/drawable-ldrtl-hdpi/ic_ab_dialer_holo_dark.png
Binary files differ
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
new file mode 100644
index 000000000..6020b3de1
--- /dev/null
+++ b/InCallUI/res/drawable-ldrtl-mdpi/ic_ab_dialer_holo_dark.png
Binary files differ
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
new file mode 100644
index 000000000..c42e7e391
--- /dev/null
+++ b/InCallUI/res/drawable-ldrtl-xhdpi/ic_ab_dialer_holo_dark.png
Binary files differ
diff --git a/InCallUI/res/drawable-mdpi/ic_ab_dialer_holo_dark.png b/InCallUI/res/drawable-mdpi/ic_ab_dialer_holo_dark.png
new file mode 100644
index 000000000..51ad9e375
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/ic_ab_dialer_holo_dark.png
Binary files differ
diff --git a/InCallUI/res/drawable-mdpi/ic_bluetooth_holo_dark.png b/InCallUI/res/drawable-mdpi/ic_bluetooth_holo_dark.png
new file mode 100644
index 000000000..fb69031dc
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/ic_bluetooth_holo_dark.png
Binary files differ
diff --git a/InCallUI/res/drawable-xhdpi/ic_ab_dialer_holo_dark.png b/InCallUI/res/drawable-xhdpi/ic_ab_dialer_holo_dark.png
new file mode 100644
index 000000000..3f43a8221
--- /dev/null
+++ b/InCallUI/res/drawable-xhdpi/ic_ab_dialer_holo_dark.png
Binary files differ
diff --git a/InCallUI/res/drawable-xhdpi/ic_bluetooth_holo_dark.png b/InCallUI/res/drawable-xhdpi/ic_bluetooth_holo_dark.png
new file mode 100644
index 000000000..24cb8939d
--- /dev/null
+++ b/InCallUI/res/drawable-xhdpi/ic_bluetooth_holo_dark.png
Binary files differ
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;
}