1/*
2 * Copyright 2014, The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.telecom;
18
19import android.content.Context;
20import android.content.Intent;
21import android.media.AudioAttributes;
22import android.media.session.MediaSession;
23import android.os.Handler;
24import android.os.Looper;
25import android.os.Message;
26import android.telecom.Log;
27import android.view.KeyEvent;
28
29/**
30 * Static class to handle listening to the headset media buttons.
31 */
32public class HeadsetMediaButton extends CallsManagerListenerBase {
33
34    // Types of media button presses
35    static final int SHORT_PRESS = 1;
36    static final int LONG_PRESS = 2;
37
38    private static final AudioAttributes AUDIO_ATTRIBUTES = new AudioAttributes.Builder()
39            .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
40            .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION).build();
41
42    private static final int MSG_MEDIA_SESSION_INITIALIZE = 0;
43    private static final int MSG_MEDIA_SESSION_SET_ACTIVE = 1;
44
45    private final MediaSession.Callback mSessionCallback = new MediaSession.Callback() {
46        @Override
47        public boolean onMediaButtonEvent(Intent intent) {
48            KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
49            Log.v(this, "SessionCallback.onMediaButton()...  event = %s.", event);
50            if ((event != null) && ((event.getKeyCode() == KeyEvent.KEYCODE_HEADSETHOOK) ||
51                                    (event.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE))) {
52                synchronized (mLock) {
53                    Log.v(this, "SessionCallback: HEADSETHOOK/MEDIA_PLAY_PAUSE");
54                    boolean consumed = handleCallMediaButton(event);
55                    Log.v(this, "==> handleCallMediaButton(): consumed = %b.", consumed);
56                    return consumed;
57                }
58            }
59            return true;
60        }
61    };
62
63    private final Handler mMediaSessionHandler = new Handler(Looper.getMainLooper()) {
64        @Override
65        public void handleMessage(Message msg) {
66            switch (msg.what) {
67                case MSG_MEDIA_SESSION_INITIALIZE: {
68                    MediaSession session = new MediaSession(
69                            mContext,
70                            HeadsetMediaButton.class.getSimpleName());
71                    session.setCallback(mSessionCallback);
72                    session.setFlags(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY
73                            | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
74                    session.setPlaybackToLocal(AUDIO_ATTRIBUTES);
75                    mSession = session;
76                    break;
77                }
78                case MSG_MEDIA_SESSION_SET_ACTIVE: {
79                    if (mSession != null) {
80                        boolean activate = msg.arg1 != 0;
81                        if (activate != mSession.isActive()) {
82                            mSession.setActive(activate);
83                        }
84                    }
85                    break;
86                }
87                default:
88                    break;
89            }
90        }
91    };
92
93    private final Context mContext;
94    private final CallsManager mCallsManager;
95    private final TelecomSystem.SyncRoot mLock;
96    private MediaSession mSession;
97    private KeyEvent mLastHookEvent;
98
99    public HeadsetMediaButton(
100            Context context,
101            CallsManager callsManager,
102            TelecomSystem.SyncRoot lock) {
103        mContext = context;
104        mCallsManager = callsManager;
105        mLock = lock;
106
107        // Create a MediaSession but don't enable it yet. This is a
108        // replacement for MediaButtonReceiver
109        mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_INITIALIZE).sendToTarget();
110    }
111
112    /**
113     * Handles the wired headset button while in-call.
114     *
115     * @return true if we consumed the event.
116     */
117    private boolean handleCallMediaButton(KeyEvent event) {
118        Log.d(this, "handleCallMediaButton()...%s %s", event.getAction(), event.getRepeatCount());
119
120        // Save ACTION_DOWN Event temporarily.
121        if (event.getAction() == KeyEvent.ACTION_DOWN) {
122            mLastHookEvent = event;
123        }
124
125        if (event.isLongPress()) {
126            return mCallsManager.onMediaButton(LONG_PRESS);
127        } else if (event.getAction() == KeyEvent.ACTION_UP) {
128            // We should not judge SHORT_PRESS by ACTION_UP event repeatCount, because it always
129            // return 0.
130            // Actually ACTION_DOWN event repeatCount only increases when LONG_PRESS performed.
131            if (mLastHookEvent != null && mLastHookEvent.getRepeatCount() == 0) {
132                return mCallsManager.onMediaButton(SHORT_PRESS);
133            }
134        }
135
136        if (event.getAction() != KeyEvent.ACTION_DOWN) {
137            mLastHookEvent = null;
138        }
139
140        return true;
141    }
142
143    /** ${inheritDoc} */
144    @Override
145    public void onCallAdded(Call call) {
146        if (call.isExternalCall()) {
147            return;
148        }
149        mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 1, 0).sendToTarget();
150    }
151
152    /** ${inheritDoc} */
153    @Override
154    public void onCallRemoved(Call call) {
155        if (call.isExternalCall()) {
156            return;
157        }
158        if (!mCallsManager.hasAnyCalls()) {
159            mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 0, 0).sendToTarget();
160        }
161    }
162
163    /** ${inheritDoc} */
164    @Override
165    public void onExternalCallChanged(Call call, boolean isExternalCall) {
166        if (isExternalCall) {
167            onCallRemoved(call);
168        } else {
169            onCallAdded(call);
170        }
171    }
172}
173