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