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