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.IBinder;
24import android.os.RemoteException;
25import android.util.Log;
26
27import java.util.ArrayList;
28import java.util.List;
29
30/**
31 * This class provides the public APIs to control the Bluetooth AVRCP Controller
32 * profile.
33 *
34 *<p>BluetoothAvrcpController is a proxy object for controlling the Bluetooth AVRCP
35 * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
36 * the BluetoothAvrcpController proxy object.
37 *
38 * {@hide}
39 */
40public final class BluetoothAvrcpController implements BluetoothProfile {
41    private static final String TAG = "BluetoothAvrcpController";
42    private static final boolean DBG = true;
43    private static final boolean VDBG = false;
44
45    /**
46     * Intent used to broadcast the change in connection state of the AVRCP Controller
47     * profile.
48     *
49     * <p>This intent will have 3 extras:
50     * <ul>
51     *   <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
52     *   <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
53     *   <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
54     * </ul>
55     *
56     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
57     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
58     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
59     *
60     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
61     * receive.
62     */
63    public static final String ACTION_CONNECTION_STATE_CHANGED =
64        "android.bluetooth.acrcp-controller.profile.action.CONNECTION_STATE_CHANGED";
65
66    private Context mContext;
67    private ServiceListener mServiceListener;
68    private IBluetoothAvrcpController mService;
69    private BluetoothAdapter mAdapter;
70
71    final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
72            new IBluetoothStateChangeCallback.Stub() {
73                public void onBluetoothStateChange(boolean up) {
74                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
75                    if (!up) {
76                        if (VDBG) Log.d(TAG,"Unbinding service...");
77                        synchronized (mConnection) {
78                            try {
79                                mService = null;
80                                mContext.unbindService(mConnection);
81                            } catch (Exception re) {
82                                Log.e(TAG,"",re);
83                            }
84                        }
85                    } else {
86                        synchronized (mConnection) {
87                            try {
88                                if (mService == null) {
89                                    if (VDBG) Log.d(TAG,"Binding service...");
90                                    doBind();
91                                }
92                            } catch (Exception re) {
93                                Log.e(TAG,"",re);
94                            }
95                        }
96                    }
97                }
98        };
99
100    /**
101     * Create a BluetoothAvrcpController proxy object for interacting with the local
102     * Bluetooth AVRCP service.
103     *
104     */
105    /*package*/ BluetoothAvrcpController(Context context, ServiceListener l) {
106        mContext = context;
107        mServiceListener = l;
108        mAdapter = BluetoothAdapter.getDefaultAdapter();
109        IBluetoothManager mgr = mAdapter.getBluetoothManager();
110        if (mgr != null) {
111            try {
112                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
113            } catch (RemoteException e) {
114                Log.e(TAG,"",e);
115            }
116        }
117
118        doBind();
119    }
120
121    boolean doBind() {
122        Intent intent = new Intent(IBluetoothAvrcpController.class.getName());
123        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
124        intent.setComponent(comp);
125        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
126                android.os.Process.myUserHandle())) {
127            Log.e(TAG, "Could not bind to Bluetooth AVRCP Controller Service with " + intent);
128            return false;
129        }
130        return true;
131    }
132
133    /*package*/ void close() {
134        mServiceListener = null;
135        IBluetoothManager mgr = mAdapter.getBluetoothManager();
136        if (mgr != null) {
137            try {
138                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
139            } catch (Exception e) {
140                Log.e(TAG,"",e);
141            }
142        }
143
144        synchronized (mConnection) {
145            if (mService != null) {
146                try {
147                    mService = null;
148                    mContext.unbindService(mConnection);
149                } catch (Exception re) {
150                    Log.e(TAG,"",re);
151                }
152            }
153        }
154    }
155
156    public void finalize() {
157        close();
158    }
159
160    /**
161     * {@inheritDoc}
162     */
163    public List<BluetoothDevice> getConnectedDevices() {
164        if (VDBG) log("getConnectedDevices()");
165        if (mService != null && isEnabled()) {
166            try {
167                return mService.getConnectedDevices();
168            } catch (RemoteException e) {
169                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
170                return new ArrayList<BluetoothDevice>();
171            }
172        }
173        if (mService == null) Log.w(TAG, "Proxy not attached to service");
174        return new ArrayList<BluetoothDevice>();
175    }
176
177    /**
178     * {@inheritDoc}
179     */
180    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
181        if (VDBG) log("getDevicesMatchingStates()");
182        if (mService != null && isEnabled()) {
183            try {
184                return mService.getDevicesMatchingConnectionStates(states);
185            } catch (RemoteException e) {
186                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
187                return new ArrayList<BluetoothDevice>();
188            }
189        }
190        if (mService == null) Log.w(TAG, "Proxy not attached to service");
191        return new ArrayList<BluetoothDevice>();
192    }
193
194    /**
195     * {@inheritDoc}
196     */
197    public int getConnectionState(BluetoothDevice device) {
198        if (VDBG) log("getState(" + device + ")");
199        if (mService != null && isEnabled()
200            && isValidDevice(device)) {
201            try {
202                return mService.getConnectionState(device);
203            } catch (RemoteException e) {
204                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
205                return BluetoothProfile.STATE_DISCONNECTED;
206            }
207        }
208        if (mService == null) Log.w(TAG, "Proxy not attached to service");
209        return BluetoothProfile.STATE_DISCONNECTED;
210    }
211
212    public void sendPassThroughCmd(BluetoothDevice device, int keyCode, int keyState) {
213        if (DBG) Log.d(TAG, "sendPassThroughCmd");
214        if (mService != null && isEnabled()) {
215            try {
216                mService.sendPassThroughCmd(device, keyCode, keyState);
217                return;
218            } catch (RemoteException e) {
219                Log.e(TAG, "Error talking to BT service in sendPassThroughCmd()", e);
220                return;
221            }
222        }
223        if (mService == null) Log.w(TAG, "Proxy not attached to service");
224    }
225
226    private final ServiceConnection mConnection = new ServiceConnection() {
227        public void onServiceConnected(ComponentName className, IBinder service) {
228            if (DBG) Log.d(TAG, "Proxy object connected");
229            mService = IBluetoothAvrcpController.Stub.asInterface(service);
230
231            if (mServiceListener != null) {
232                mServiceListener.onServiceConnected(BluetoothProfile.AVRCP_CONTROLLER,
233                        BluetoothAvrcpController.this);
234            }
235        }
236        public void onServiceDisconnected(ComponentName className) {
237            if (DBG) Log.d(TAG, "Proxy object disconnected");
238            mService = null;
239            if (mServiceListener != null) {
240                mServiceListener.onServiceDisconnected(BluetoothProfile.AVRCP_CONTROLLER);
241            }
242        }
243    };
244
245    private boolean isEnabled() {
246       if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
247       return false;
248    }
249
250    private boolean isValidDevice(BluetoothDevice device) {
251       if (device == null) return false;
252
253       if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
254       return false;
255    }
256
257    private static void log(String msg) {
258      Log.d(TAG, msg);
259    }
260}
261