am deb8c897: Move headset hook from Telephony to Telecomm. (1/2)
* commit 'deb8c89707c604d4f9f32e476a58bd10a68293ff':
Move headset hook from Telephony to Telecomm. (1/2)
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 4b6260a..0c46053 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -41,4 +41,12 @@
The user will be able to send text messages using the phone number.
[CHAR LIMIT=60] -->
<string name="notification_missedCall_message">Message</string>
+
+ <!-- Content description of the call muted notification icon for
+ accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_call_muted">Call muted.</string>
+
+ <!-- Content description of the speakerphone enabled notification icon for
+ accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
+ <string name="accessibility_speakerphone_enabled">Speakerphone enabled.</string>
</resources>
diff --git a/src/com/android/telecomm/CallAudioManager.java b/src/com/android/telecomm/CallAudioManager.java
index 43b102b..865e3ca 100644
--- a/src/com/android/telecomm/CallAudioManager.java
+++ b/src/com/android/telecomm/CallAudioManager.java
@@ -29,21 +29,23 @@
final class CallAudioManager extends CallsManagerListenerBase {
private static final int STREAM_NONE = -1;
+ private final StatusBarNotifier mStatusBarNotifier;
private final AudioManager mAudioManager;
private final WiredHeadsetManager mWiredHeadsetManager;
private final BluetoothManager mBluetoothManager;
+
private CallAudioState mAudioState;
private int mAudioFocusStreamType;
private boolean mIsRinging;
private boolean mIsTonePlaying;
private boolean mWasSpeakerOn;
- CallAudioManager() {
- Context context = TelecommApp.getInstance();
+ CallAudioManager(Context context, StatusBarNotifier statusBarNotifier) {
+ mStatusBarNotifier = statusBarNotifier;
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mWiredHeadsetManager = new WiredHeadsetManager(this);
mBluetoothManager = new BluetoothManager(context, this);
- mAudioState = getInitialAudioState(null);
+ saveAudioState(getInitialAudioState(null));
mAudioFocusStreamType = STREAM_NONE;
}
@@ -100,6 +102,10 @@
updateAudioForForegroundCall();
}
+ void toggleMute() {
+ mute(!mAudioState.isMuted);
+ }
+
void mute(boolean shouldMute) {
Log.v(this, "mute, shouldMute: %b", shouldMute);
@@ -206,9 +212,15 @@
return mBluetoothManager.isBluetoothAvailable();
}
+ private void saveAudioState(CallAudioState audioState) {
+ mAudioState = audioState;
+ mStatusBarNotifier.notifyMute(mAudioState.isMuted);
+ mStatusBarNotifier.notifySpeakerphone(mAudioState.route == CallAudioState.ROUTE_SPEAKER);
+ }
+
private void setSystemAudioState(boolean isMuted, int route, int supportedRouteMask) {
CallAudioState oldAudioState = mAudioState;
- mAudioState = new CallAudioState(isMuted, route, supportedRouteMask);
+ saveAudioState(new CallAudioState(isMuted, route, supportedRouteMask));
Log.i(this, "changing audio state from %s to %s", oldAudioState, mAudioState);
// Mute.
diff --git a/src/com/android/telecomm/CallsManager.java b/src/com/android/telecomm/CallsManager.java
index e8b49e5..a2aa045 100644
--- a/src/com/android/telecomm/CallsManager.java
+++ b/src/com/android/telecomm/CallsManager.java
@@ -83,6 +83,7 @@
private final CallAudioManager mCallAudioManager;
private final Ringer mRinger;
private final Set<CallsManagerListener> mListeners = new HashSet<>();
+ private final HeadsetMediaButton mHeadsetMediaButton;
/**
* The call the user is currently interacting with. This is the call that should have audio
@@ -101,10 +102,13 @@
private CallsManager() {
TelecommApp app = TelecommApp.getInstance();
- mCallAudioManager = new CallAudioManager();
+ StatusBarNotifier statusBarNotifier = new StatusBarNotifier(app, this);
+ mCallAudioManager = new CallAudioManager(app, statusBarNotifier);
InCallTonePlayer.Factory playerFactory = new InCallTonePlayer.Factory(mCallAudioManager);
mRinger = new Ringer(mCallAudioManager, this, playerFactory, app);
+ mHeadsetMediaButton = new HeadsetMediaButton(app, this);
+ mListeners.add(statusBarNotifier);
mListeners.add(new CallLogManager(app));
mListeners.add(new PhoneStateBroadcaster());
mListeners.add(mInCallController);
@@ -499,6 +503,10 @@
}
}
+ boolean hasAnyCalls() {
+ return !mCalls.isEmpty();
+ }
+
boolean hasActiveOrHoldingCall() {
for (Call call : mCalls) {
CallState state = call.getState();
@@ -518,6 +526,46 @@
return false;
}
+ boolean onMediaButton(int type) {
+ if (hasAnyCalls()) {
+ if (HeadsetMediaButton.SHORT_PRESS == type) {
+ Call ringingCall = getFirstCallWithState(CallState.RINGING);
+ if (ringingCall == null) {
+ mCallAudioManager.toggleMute();
+ return true;
+ } else {
+ ringingCall.answer();
+ return true;
+ }
+ } else if (HeadsetMediaButton.LONG_PRESS == type) {
+ Log.d(this, "handleHeadsetHook: longpress -> hangup");
+ Call callToHangup = getFirstCallWithState(
+ CallState.RINGING, CallState.DIALING, CallState.ACTIVE, CallState.ON_HOLD);
+ if (callToHangup != null) {
+ callToHangup.disconnect();
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns the first call that it finds with the given states. The states are treated as having
+ * priority order so that any call with the first state will be returned before any call with
+ * states listed later in the parameter list.
+ */
+ private Call getFirstCallWithState(CallState... states) {
+ for (CallState currentState : states) {
+ for (Call call : mCalls) {
+ if (currentState == call.getState()) {
+ return call;
+ }
+ }
+ }
+ return null;
+ }
+
/**
* Adds the specified call to the main list of live calls.
*
diff --git a/src/com/android/telecomm/HeadsetMediaButton.java b/src/com/android/telecomm/HeadsetMediaButton.java
new file mode 100644
index 0000000..db34f59
--- /dev/null
+++ b/src/com/android/telecomm/HeadsetMediaButton.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2014, 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.telecomm;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioManager;
+import android.media.session.MediaSession;
+import android.media.session.MediaSessionManager;
+import android.view.KeyEvent;
+
+/**
+ * Static class to handle listening to the headset media buttons.
+ */
+final class HeadsetMediaButton {
+
+ /**
+ * Broadcast receiver for the ACTION_MEDIA_BUTTON broadcast intent.
+ *
+ * This functionality isn't lumped in with the other intents in TelecommBroadcastReceiver
+ * because we instantiate this as a totally separate BroadcastReceiver instance, since we need
+ * to manually adjust its IntentFilter's priority (to make sure we get these intents *before*
+ * the media player.)
+ */
+ private final class MediaButtonBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+ Log.v(this, "MediaButtonBroadcastReceiver.onReceive()... event = %s.", event);
+ if ((event != null) && (event.getKeyCode() == KeyEvent.KEYCODE_HEADSETHOOK)) {
+ boolean consumed = handleHeadsetHook(event);
+ Log.v(this, "==> handleHeadsetHook(): consumed = %b.", consumed);
+ if (consumed) {
+ abortBroadcast();
+ }
+ } else {
+ if (CallsManager.getInstance().hasAnyCalls()) {
+ // If the phone is anything other than completely idle, then we consume and
+ // ignore any media key events, otherwise it is too easy to accidentally start
+ // playing music while a phone call is in progress.
+ Log.v(this, "MediaButtonBroadcastReceiver: consumed");
+ abortBroadcast();
+ }
+ }
+ }
+ }
+
+ // Types of media button presses
+ static final int SHORT_PRESS = 1;
+ static final int LONG_PRESS = 2;
+
+ private final MediaSession.Callback mSessionCallback = new MediaSession.Callback() {
+ @Override
+ public void onMediaButtonEvent(Intent intent) {
+ KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+ Log.v(this, "SessionCallback.onMediaButton()... event = %s.", event);
+ if ((event != null) && (event.getKeyCode() == KeyEvent.KEYCODE_HEADSETHOOK)) {
+ Log.v(this, "SessionCallback: HEADSETHOOK");
+ boolean consumed = handleHeadsetHook(event);
+ Log.v(this, "==> handleHeadsetHook(): consumed = %b.", consumed);
+ }
+ }
+ };
+
+ private final MediaButtonBroadcastReceiver mMediaButtonReceiver =
+ new MediaButtonBroadcastReceiver();
+
+ private final CallsManager mCallsManager;
+
+ private final MediaSession mSession;
+
+ HeadsetMediaButton(Context context, CallsManager callsManager) {
+ mCallsManager = callsManager;
+
+ // Use a separate receiver (from TelecommBroadcastReceiver) for ACTION_MEDIA_BUTTON
+ // broadcasts, since we need to manually adjust its priority (to make sure we get these
+ // intents *before* the media player.)
+ IntentFilter mediaButtonIntentFilter =
+ new IntentFilter(Intent.ACTION_MEDIA_BUTTON);
+
+ // Make sure we're higher priority than the media player's MediaButtonIntentReceiver (which
+ // currently has the default priority of zero; see apps/Music/AndroidManifest.xml.)
+ mediaButtonIntentFilter.setPriority(1);
+
+ context.registerReceiver(mMediaButtonReceiver, mediaButtonIntentFilter);
+
+ // register the component so it gets priority for calls
+ AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+ am.registerMediaButtonEventReceiverForCalls(new ComponentName(context.getPackageName(),
+ MediaButtonBroadcastReceiver.class.getName()));
+
+ // Register a MediaSession but don't enable it yet. This is a
+ // replacement for MediaButtonReceiver
+ MediaSessionManager msm =
+ (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
+ mSession = msm.createSession(HeadsetMediaButton.class.getSimpleName());
+ mSession.addCallback(mSessionCallback);
+ mSession.setFlags(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY
+ | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
+ }
+
+ /**
+ * Handles the wired headset button while in-call.
+ *
+ * @return true if we consumed the event.
+ */
+ private boolean handleHeadsetHook(KeyEvent event) {
+ Log.d(this, "handleHeadsetHook()...%s %s", event.getAction(), event.getRepeatCount());
+
+ if (event.isLongPress()) {
+ return mCallsManager.onMediaButton(LONG_PRESS);
+ } else if (event.getAction() == KeyEvent.ACTION_UP && event.getRepeatCount() == 0) {
+ return mCallsManager.onMediaButton(SHORT_PRESS);
+ }
+
+ return true;
+ }
+}
diff --git a/src/com/android/telecomm/StatusBarNotifier.java b/src/com/android/telecomm/StatusBarNotifier.java
new file mode 100644
index 0000000..eea187f
--- /dev/null
+++ b/src/com/android/telecomm/StatusBarNotifier.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2014 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.telecomm;
+
+import android.app.StatusBarManager;
+import android.content.Context;
+
+/**
+ * Manages the special status bar notifications used by the phone app.
+ */
+final class StatusBarNotifier extends CallsManagerListenerBase {
+ private static final String SLOT_MUTE = "mute";
+ private static final String SLOT_SPEAKERPHONE = "speakerphone";
+
+ private final Context mContext;
+ private final CallsManager mCallsManager;
+ private final StatusBarManager mStatusBarManager;
+
+ private boolean mIsShowingMute;
+ private boolean mIsShowingSpeakerphone;
+
+ StatusBarNotifier(Context context, CallsManager callsManager) {
+ mContext = context;
+ mCallsManager = callsManager;
+ mStatusBarManager = (StatusBarManager) context.getSystemService(Context.STATUS_BAR_SERVICE);
+ }
+
+ /** ${inheritDoc} */
+ @Override
+ public void onCallRemoved(Call call) {
+ if (!mCallsManager.hasAnyCalls()) {
+ notifyMute(false);
+ notifySpeakerphone(false);
+ }
+ }
+
+ void notifyMute(boolean isMuted) {
+ // Never display anything if there are no calls.
+ if (!mCallsManager.hasAnyCalls()) {
+ isMuted = false;
+ }
+
+ if (mIsShowingMute == isMuted) {
+ return;
+ }
+
+ Log.d(this, "Mute status bar icon being set to %b", isMuted);
+
+ if (isMuted) {
+ mStatusBarManager.setIcon(
+ SLOT_MUTE,
+ android.R.drawable.stat_notify_call_mute,
+ 0, /* iconLevel */
+ mContext.getString(R.string.accessibility_call_muted));
+ } else {
+ mStatusBarManager.removeIcon(SLOT_MUTE);
+ }
+ mIsShowingMute = isMuted;
+ }
+
+ void notifySpeakerphone(boolean isSpeakerphone) {
+ // Never display anything if there are no calls.
+ if (!mCallsManager.hasAnyCalls()) {
+ isSpeakerphone = false;
+ }
+
+ if (mIsShowingSpeakerphone == isSpeakerphone) {
+ return;
+ }
+
+ Log.d(this, "Speakerphone status bar icon being set to %b", isSpeakerphone);
+
+ if (isSpeakerphone) {
+ mStatusBarManager.setIcon(
+ SLOT_SPEAKERPHONE,
+ android.R.drawable.stat_sys_speakerphone,
+ 0, /* iconLevel */
+ mContext.getString(R.string.accessibility_speakerphone_enabled));
+ } else {
+ mStatusBarManager.removeIcon(SLOT_SPEAKERPHONE);
+ }
+ mIsShowingSpeakerphone = isSpeakerphone;
+ }
+}