• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * ThreadService.java - libwebsockets test service for Android
3  *
4  * Copyright (C) 2016 Alexander Bruines <alexander.bruines@gmail.com>
5  *
6  * This file is made available under the Creative Commons CC0 1.0
7  * Universal Public Domain Dedication.
8  *
9  * The person who associated a work with this deed has dedicated
10  * the work to the public domain by waiving all of his or her rights
11  * to the work worldwide under copyright law, including all related
12  * and neighboring rights, to the extent allowed by law. You can copy,
13  * modify, distribute and perform the work, even for commercial purposes,
14  * all without asking permission.
15  *
16  * The test apps are intended to be adapted for use in your code, which
17  * may be proprietary.  So unlike the library itself, they are licensed
18  * Public Domain.
19  */
20 
21 package org.libwebsockets.client;
22 
23 import android.app.Service;
24 import android.content.Intent;
25 import android.os.Handler;
26 import android.os.IBinder;
27 import android.os.Message;
28 import android.os.Messenger;
29 import android.os.RemoteException;
30 import android.util.Log;
31 
32 import java.lang.ref.WeakReference;
33 
34 public abstract class ThreadService extends Service {
35     /** Messages that can be send to the Service: **/
36     public final static int MSG_SET_OUTPUT_HANDLER  = 1001;
37     public final static int MSG_THREAD_START        = 1002;
38     public final static int MSG_THREAD_STOP         = 1003;
39     public final static int MSG_THREAD_SUSPEND      = 1004;
40     public final static int MSG_THREAD_RESUME       = 1005;
41 
42     /**
43      * Messages that may be send from the Service
44      * (Clients should handle these messages)
45      **/
46     public final static int MSG_THREAD_STARTED      = 2001;
47     public final static int MSG_THREAD_STOPPED      = 2002;
48     public final static int MSG_THREAD_SUSPENDED    = 2003;
49     public final static int MSG_THREAD_RESUMED      = 2004;
50 
51     /** Data accessed by both worker thread and the UI-thread must be synchronized **/
52     public final Object mThreadLock = new Object();;
53     public volatile boolean mMustQuit;
54     public volatile boolean mWorkThreadIsRunning;
55     public volatile boolean mMustSuspend;
56 
57     /** Handler for incoming messages **/
58     public static class InputHandler extends Handler {
59         private final WeakReference<ThreadService> mService;
InputHandler(ThreadService service)60         InputHandler(ThreadService service) {
61             mService = new WeakReference<ThreadService>(service);
62         }
63         @Override
handleMessage(Message msg)64         public void handleMessage(Message msg) {
65             ThreadService service = mService.get();
66             if(service != null) {
67                 service.handleInputMessage(msg);
68             }
69         }
70     }
71 
72     /**
73      * Interface and Handler for outgoing messages to clients of this service.
74      * (Must be implemented by the client.)
75      */
76     public interface OutputInterface {
handleOutputMessage(Message message)77         void handleOutputMessage(Message message);
78     }
79     public static class OutputHandler extends Handler {
80         // Notice that we do NOT use a WeakReference here
81         // (If we did the service would lose mOutputMessenger the moment
82         // that garbage collection is performed by the Java VM)
83         private final OutputInterface mInterface;
OutputHandler(OutputInterface object)84         OutputHandler(OutputInterface object) {
85             mInterface = object;
86         }
87         @Override
handleMessage(Message msg)88         public void handleMessage(Message msg) {
89             mInterface.handleOutputMessage(msg);
90         }
91     }
92 
93     /** The Messengers used to communicate with the clients of this service **/
94     public final Messenger mInputMessenger = new Messenger(new InputHandler(this));
95     public Messenger mOutputMessenger;
96 
97     /** The worker thread and its runnable **/
98     public static class WorkerThreadRunnable implements Runnable {
99         private final WeakReference<ThreadService> mService;
WorkerThreadRunnable(ThreadService service)100         WorkerThreadRunnable(ThreadService service){
101             mService = new WeakReference<ThreadService>(service);
102         }
103         @Override
run()104         public void run() {
105             ThreadService service = mService.get();
106             if(service != null) {
107                 service.mWorkThreadIsRunning = true;
108                 service.workerThreadRun();
109                 service.mWorkThreadIsRunning = false;
110             }
111         }
112     }
113     public Thread mWorkerThread;
114 
115     /** Handle incoming messages from the client **/
handleInputMessage(Message msg)116     public void handleInputMessage(Message msg) {
117         try {
118             Message m;
119             switch(msg.what) {
120                 case MSG_SET_OUTPUT_HANDLER:
121                     // set the output messenger then
122                     // send a message indicating the thread status
123                     mOutputMessenger = msg.replyTo;
124                     break;
125                 case MSG_THREAD_START:
126                     try {
127                         // reset thread vars
128                         synchronized (mThreadLock) {
129                             // thread allready running?
130                             if(!mWorkThreadIsRunning){
131                                 // no, start it
132                                 mMustQuit = false;
133                                 mMustSuspend = false;
134                                 mWorkerThread = new Thread(new WorkerThreadRunnable(this));
135                                 mWorkerThread.start();
136                             }
137                             else {
138                                 // yes, resume it
139                                 mMustQuit = false;
140                                 mMustSuspend = false;
141                                 mThreadLock.notifyAll();
142                             }
143                         }
144                     }
145                     catch(NullPointerException e) {
146                         e.printStackTrace();
147                     }
148                     if(mOutputMessenger != null) {
149                         m = Message.obtain(null, MSG_THREAD_STARTED, 0, 0);
150                         mOutputMessenger.send(m);
151                     }
152                     break;
153                 case MSG_THREAD_STOP:
154                     try {
155                         synchronized(mThreadLock) {
156                             if(mWorkThreadIsRunning) {
157                                 mMustQuit = true;
158                                 mMustSuspend = false;
159                                 mThreadLock.notifyAll();
160                             }
161                         }
162                         mWorkerThread.join();
163                     }
164                     catch(InterruptedException e) {
165                         Log.e("ThreadService","handleInputMessage join() interrupted");
166                     }
167                     if(mOutputMessenger != null) {
168                         m = Message.obtain(null, MSG_THREAD_STOPPED, 0, 0);
169                         mOutputMessenger.send(m);
170                     }
171                     break;
172                 case MSG_THREAD_SUSPEND:
173                     synchronized (mThreadLock) {
174                         if(mWorkThreadIsRunning) {
175                             mMustSuspend = true;
176                         }
177                     }
178                     if(mOutputMessenger != null) {
179                         m = Message.obtain(null, MSG_THREAD_SUSPENDED, 0, 0);
180                         mOutputMessenger.send(m);
181                     }
182                     break;
183                 case MSG_THREAD_RESUME:
184                     synchronized (mThreadLock) {
185                         if(mWorkThreadIsRunning) {
186                             mMustSuspend = false;
187                             mThreadLock.notifyAll();
188                         }
189                     }
190                     if(mOutputMessenger != null) {
191                         m = Message.obtain(null, MSG_THREAD_RESUMED, 0, 0);
192                         mOutputMessenger.send(m);
193                     }
194                     break;
195                 default:
196                     break;
197             }
198         }
199         catch(RemoteException e) {
200             e.printStackTrace();
201         }
202     }
203 
204     /**
205      * This can be called from the JNI functions to send output messages to the client
206      */
sendMessage(int msg, Object obj)207     public void sendMessage(int msg, Object obj){
208         Message m = Message.obtain(null, msg, 0, 0);
209         m.obj = obj;
210         try {
211             mOutputMessenger.send(m);
212         }
213         catch(RemoteException e) {
214             e.printStackTrace();
215         }
216     }
217 
218     /** The run() function for the worker thread **/
workerThreadRun()219     public abstract void workerThreadRun();
220 
221     /**
222      *  Called when the service is being created.
223      *  ie. When the first client calls bindService() or startService().
224      **/
225     @Override
onCreate()226     public void onCreate() {
227         super.onCreate();
228         // initialize variables
229         mWorkThreadIsRunning = false;
230         mMustQuit = false;
231         mOutputMessenger = null;
232         mWorkerThread = null;
233     }
234 
235     /**
236      *  Called when the first client is binding to the service with bindService()
237      *
238      *  If the service was started with bindService() it will automatically stop when the last
239      *  client unbinds from the service. If you want the service to continue running even if it
240      *  is not bound to anything then start the service with startService() before
241      *  calling bindService(). In this case stopService() must be called after unbinding
242      *  to stop the service.
243      */
244     @Override
onBind(Intent intent)245     public IBinder onBind(Intent intent) {
246         return mInputMessenger.getBinder();
247     }
248 
249     /** Called if the service is started with startService(). */
250     @Override
onStartCommand(Intent intent, int flags, int startId)251     public int onStartCommand(Intent intent, int flags, int startId) {
252         return START_STICKY;
253     }
254 
255     /** Called when the first client is binds to the service with bindService() */
256     @Override
onRebind(Intent intent)257     public void onRebind(Intent intent) {}
258 
259     /** Called when all clients have unbound with unbindService() */
260     @Override
onUnbind(Intent intent)261     public boolean onUnbind(Intent intent) {
262         //mOutputMessenger = null;
263         return false; // do not allow to rebind.
264     }
265 
266     /** Called when the service is no longer used and is being destroyed */
267     @Override
onDestroy()268     public void onDestroy() {
269         super.onDestroy();
270         try {
271             if(mWorkThreadIsRunning){
272                 synchronized(mThreadLock) {
273                     mMustQuit = true;
274                     mMustSuspend = false;
275                     mThreadLock.notifyAll();
276                 }
277                 mWorkerThread.join();
278             }
279         }
280         catch(NullPointerException | InterruptedException e) {
281             e.printStackTrace();
282         }
283     }
284 }
285