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.client.map;
18
19import android.bluetooth.BluetoothAdapter;
20import android.bluetooth.BluetoothServerSocket;
21import android.bluetooth.BluetoothSocket;
22import android.os.Handler;
23import android.os.Message;
24import android.os.ParcelUuid;
25import android.util.Log;
26import android.util.SparseArray;
27
28import java.io.IOException;
29import java.io.InterruptedIOException;
30import java.lang.ref.WeakReference;
31
32import javax.obex.ServerSession;
33
34class BluetoothMnsService {
35
36    private static final String TAG = "BluetoothMnsService";
37
38    private static final ParcelUuid MAP_MNS =
39            ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB");
40
41    static final int MSG_EVENT = 1;
42
43    /* for BluetoothMasClient */
44    static final int EVENT_REPORT = 1001;
45
46    /* these are shared across instances */
47    static private SparseArray<Handler> mCallbacks = null;
48    static private SocketAcceptThread mAcceptThread = null;
49    static private Handler mSessionHandler = null;
50    static private BluetoothServerSocket mServerSocket = null;
51
52    private static class SessionHandler extends Handler {
53
54        private final WeakReference<BluetoothMnsService> mService;
55
56        SessionHandler(BluetoothMnsService service) {
57            mService = new WeakReference<BluetoothMnsService>(service);
58        }
59
60        @Override
61        public void handleMessage(Message msg) {
62            Log.d(TAG, "Handler: msg: " + msg.what);
63
64            switch (msg.what) {
65                case MSG_EVENT:
66                    int instanceId = msg.arg1;
67
68                    synchronized (mCallbacks) {
69                        Handler cb = mCallbacks.get(instanceId);
70
71                        if (cb != null) {
72                            BluetoothMapEventReport ev = (BluetoothMapEventReport) msg.obj;
73                            cb.obtainMessage(EVENT_REPORT, ev).sendToTarget();
74                        } else {
75                            Log.w(TAG, "Got event for instance which is not registered: "
76                                    + instanceId);
77                        }
78                    }
79                    break;
80            }
81        }
82    }
83
84    private static class SocketAcceptThread extends Thread {
85
86        private boolean mInterrupted = false;
87
88        @Override
89        public void run() {
90
91            if (mServerSocket != null) {
92                Log.w(TAG, "Socket already created, exiting");
93                return;
94            }
95
96            try {
97                BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
98                mServerSocket = adapter.listenUsingEncryptedRfcommWithServiceRecord(
99                        "MAP Message Notification Service", MAP_MNS.getUuid());
100            } catch (IOException e) {
101                mInterrupted = true;
102                Log.e(TAG, "I/O exception when trying to create server socket", e);
103            }
104
105            while (!mInterrupted) {
106                try {
107                    Log.v(TAG, "waiting to accept connection...");
108
109                    BluetoothSocket sock = mServerSocket.accept();
110
111                    Log.v(TAG, "new incoming connection from "
112                            + sock.getRemoteDevice().getName());
113
114                    // session will live until closed by remote
115                    BluetoothMnsObexServer srv = new BluetoothMnsObexServer(mSessionHandler);
116                    BluetoothMapRfcommTransport transport = new BluetoothMapRfcommTransport(
117                            sock);
118                    new ServerSession(transport, srv, null);
119                } catch (IOException ex) {
120                    Log.v(TAG, "I/O exception when waiting to accept (aborted?)");
121                    mInterrupted = true;
122                }
123            }
124
125            if (mServerSocket != null) {
126                try {
127                    mServerSocket.close();
128                } catch (IOException e) {
129                    // do nothing
130                }
131
132                mServerSocket = null;
133            }
134        }
135    }
136
137    BluetoothMnsService() {
138        Log.v(TAG, "BluetoothMnsService()");
139
140        if (mCallbacks == null) {
141            Log.v(TAG, "BluetoothMnsService(): allocating callbacks");
142            mCallbacks = new SparseArray<Handler>();
143        }
144
145        if (mSessionHandler == null) {
146            Log.v(TAG, "BluetoothMnsService(): allocating session handler");
147            mSessionHandler = new SessionHandler(this);
148        }
149    }
150
151    public void registerCallback(int instanceId, Handler callback) {
152        Log.v(TAG, "registerCallback()");
153
154        synchronized (mCallbacks) {
155            mCallbacks.put(instanceId, callback);
156
157            if (mAcceptThread == null) {
158                Log.v(TAG, "registerCallback(): starting MNS server");
159                mAcceptThread = new SocketAcceptThread();
160                mAcceptThread.setName("BluetoothMnsAcceptThread");
161                mAcceptThread.start();
162            }
163        }
164    }
165
166    public void unregisterCallback(int instanceId) {
167        Log.v(TAG, "unregisterCallback()");
168
169        synchronized (mCallbacks) {
170            mCallbacks.remove(instanceId);
171
172            if (mCallbacks.size() == 0) {
173                Log.v(TAG, "unregisterCallback(): shutting down MNS server");
174
175                if (mServerSocket != null) {
176                    try {
177                        mServerSocket.close();
178                    } catch (IOException e) {
179                    }
180
181                    mServerSocket = null;
182                }
183
184                mAcceptThread.interrupt();
185
186                try {
187                    mAcceptThread.join(5000);
188                } catch (InterruptedException e) {
189                }
190
191                mAcceptThread = null;
192            }
193        }
194    }
195}
196