1/*
2 * Copyright (C) 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 android.bluetooth;
18
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.ServiceConnection;
23import android.media.MediaMetadata;
24import android.media.session.PlaybackState;
25import android.os.Binder;
26import android.os.IBinder;
27import android.os.RemoteException;
28import android.util.Log;
29
30import java.util.ArrayList;
31import java.util.List;
32
33/**
34 * This class provides the public APIs to control the Bluetooth AVRCP Controller. It currently
35 * supports player information, playback support and track metadata.
36 *
37 *<p>BluetoothAvrcpController is a proxy object for controlling the Bluetooth AVRCP
38 * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
39 * the BluetoothAvrcpController proxy object.
40 *
41 * {@hide}
42 */
43public final class BluetoothAvrcpController implements BluetoothProfile {
44    private static final String TAG = "BluetoothAvrcpController";
45    private static final boolean DBG = false;
46    private static final boolean VDBG = false;
47
48    /**
49     * Intent used to broadcast the change in connection state of the AVRCP Controller
50     * profile.
51     *
52     * <p>This intent will have 3 extras:
53     * <ul>
54     *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
55     *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
56     *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
57     * </ul>
58     *
59     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
60     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
61     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
62     *
63     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
64     * receive.
65     */
66    public static final String ACTION_CONNECTION_STATE_CHANGED =
67        "android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED";
68
69    /**
70     * Intent used to broadcast the change in player application setting state on AVRCP AG.
71     *
72     * <p>This intent will have the following extras:
73     * <ul>
74     *    <li> {@link #EXTRA_PLAYER_SETTING} - {@link BluetoothAvrcpPlayerSettings} containing the
75     *    most recent player setting. </li>
76     * </ul>
77     */
78    public static final String ACTION_PLAYER_SETTING =
79        "android.bluetooth.avrcp-controller.profile.action.PLAYER_SETTING";
80
81    public static final String EXTRA_PLAYER_SETTING =
82            "android.bluetooth.avrcp-controller.profile.extra.PLAYER_SETTING";
83
84    private Context mContext;
85    private ServiceListener mServiceListener;
86    private IBluetoothAvrcpController mService;
87    private BluetoothAdapter mAdapter;
88
89    final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
90        new IBluetoothStateChangeCallback.Stub() {
91            public void onBluetoothStateChange(boolean up) {
92                if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
93                if (!up) {
94                    if (VDBG) Log.d(TAG,"Unbinding service...");
95                    synchronized (mConnection) {
96                        try {
97                            mService = null;
98                            mContext.unbindService(mConnection);
99                        } catch (Exception re) {
100                            Log.e(TAG,"",re);
101                        }
102                    }
103                } else {
104                    synchronized (mConnection) {
105                        try {
106                            if (mService == null) {
107                                if (VDBG) Log.d(TAG,"Binding service...");
108                                doBind();
109                            }
110                        } catch (Exception re) {
111                            Log.e(TAG,"",re);
112                        }
113                    }
114                }
115            }
116      };
117
118    /**
119     * Create a BluetoothAvrcpController proxy object for interacting with the local
120     * Bluetooth AVRCP service.
121     *
122     */
123    /*package*/ BluetoothAvrcpController(Context context, ServiceListener l) {
124        mContext = context;
125        mServiceListener = l;
126        mAdapter = BluetoothAdapter.getDefaultAdapter();
127        IBluetoothManager mgr = mAdapter.getBluetoothManager();
128        if (mgr != null) {
129            try {
130                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
131            } catch (RemoteException e) {
132                Log.e(TAG,"",e);
133            }
134        }
135
136        doBind();
137    }
138
139    boolean doBind() {
140        Intent intent = new Intent(IBluetoothAvrcpController.class.getName());
141        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
142        intent.setComponent(comp);
143        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
144                android.os.Process.myUserHandle())) {
145            Log.e(TAG, "Could not bind to Bluetooth AVRCP Controller Service with " + intent);
146            return false;
147        }
148        return true;
149    }
150
151    /*package*/ void close() {
152        mServiceListener = null;
153        IBluetoothManager mgr = mAdapter.getBluetoothManager();
154        if (mgr != null) {
155            try {
156                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
157            } catch (Exception e) {
158                Log.e(TAG,"",e);
159            }
160        }
161
162        synchronized (mConnection) {
163            if (mService != null) {
164                try {
165                    mService = null;
166                    mContext.unbindService(mConnection);
167                } catch (Exception re) {
168                    Log.e(TAG,"",re);
169                }
170            }
171        }
172    }
173
174    public void finalize() {
175        close();
176    }
177
178    /**
179     * {@inheritDoc}
180     */
181    public List<BluetoothDevice> getConnectedDevices() {
182        if (VDBG) log("getConnectedDevices()");
183        if (mService != null && isEnabled()) {
184            try {
185                return mService.getConnectedDevices();
186            } catch (RemoteException e) {
187                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
188                return new ArrayList<BluetoothDevice>();
189            }
190        }
191        if (mService == null) Log.w(TAG, "Proxy not attached to service");
192        return new ArrayList<BluetoothDevice>();
193    }
194
195    /**
196     * {@inheritDoc}
197     */
198    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
199        if (VDBG) log("getDevicesMatchingStates()");
200        if (mService != null && isEnabled()) {
201            try {
202                return mService.getDevicesMatchingConnectionStates(states);
203            } catch (RemoteException e) {
204                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
205                return new ArrayList<BluetoothDevice>();
206            }
207        }
208        if (mService == null) Log.w(TAG, "Proxy not attached to service");
209        return new ArrayList<BluetoothDevice>();
210    }
211
212    /**
213     * {@inheritDoc}
214     */
215    public int getConnectionState(BluetoothDevice device) {
216        if (VDBG) log("getState(" + device + ")");
217        if (mService != null && isEnabled()
218            && isValidDevice(device)) {
219            try {
220                return mService.getConnectionState(device);
221            } catch (RemoteException e) {
222                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
223                return BluetoothProfile.STATE_DISCONNECTED;
224            }
225        }
226        if (mService == null) Log.w(TAG, "Proxy not attached to service");
227        return BluetoothProfile.STATE_DISCONNECTED;
228    }
229
230    /**
231     * Gets the player application settings.
232     *
233     * @return the {@link BluetoothAvrcpPlayerSettings} or {@link null} if there is an error.
234     */
235    public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
236        if (DBG) Log.d(TAG, "getPlayerSettings");
237        BluetoothAvrcpPlayerSettings settings = null;
238        if (mService != null && isEnabled()) {
239            try {
240                settings = mService.getPlayerSettings(device);
241            } catch (RemoteException e) {
242                Log.e(TAG, "Error talking to BT service in getMetadata() " + e);
243                return null;
244            }
245        }
246        return settings;
247    }
248
249    /**
250     * Sets the player app setting for current player.
251     * returns true in case setting is supported by remote, false otherwise
252     */
253    public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
254        if (DBG) Log.d(TAG, "setPlayerApplicationSetting");
255        if (mService != null && isEnabled()) {
256            try {
257                return mService.setPlayerApplicationSetting(plAppSetting);
258            } catch (RemoteException e) {
259                Log.e(TAG, "Error talking to BT service in setPlayerApplicationSetting() " + e);
260                return false;
261            }
262        }
263        if (mService == null) Log.w(TAG, "Proxy not attached to service");
264        return false;
265    }
266
267    /*
268     * Send Group Navigation Command to Remote.
269     * possible keycode values: next_grp, previous_grp defined above
270     */
271    public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
272        Log.d(TAG, "sendGroupNavigationCmd dev = " + device + " key " + keyCode + " State = " + keyState);
273        if (mService != null && isEnabled()) {
274            try {
275                mService.sendGroupNavigationCmd(device, keyCode, keyState);
276                return;
277            } catch (RemoteException e) {
278                Log.e(TAG, "Error talking to BT service in sendGroupNavigationCmd()", e);
279                return;
280            }
281        }
282        if (mService == null) Log.w(TAG, "Proxy not attached to service");
283    }
284
285    private final ServiceConnection mConnection = new ServiceConnection() {
286        public void onServiceConnected(ComponentName className, IBinder service) {
287            if (DBG) Log.d(TAG, "Proxy object connected");
288            mService = IBluetoothAvrcpController.Stub.asInterface(Binder.allowBlocking(service));
289
290            if (mServiceListener != null) {
291                mServiceListener.onServiceConnected(BluetoothProfile.AVRCP_CONTROLLER,
292                        BluetoothAvrcpController.this);
293            }
294        }
295        public void onServiceDisconnected(ComponentName className) {
296            if (DBG) Log.d(TAG, "Proxy object disconnected");
297            mService = null;
298            if (mServiceListener != null) {
299                mServiceListener.onServiceDisconnected(BluetoothProfile.AVRCP_CONTROLLER);
300            }
301        }
302    };
303
304    private boolean isEnabled() {
305       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
306       return false;
307    }
308
309    private boolean isValidDevice(BluetoothDevice device) {
310       if (device == null) return false;
311
312       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
313       return false;
314    }
315
316    private static void log(String msg) {
317      Log.d(TAG, msg);
318    }
319}
320