1 /* 2 * Copyright (C) 2007 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 17 package com.android.server; 18 19 import android.net.LocalSocket; 20 import android.net.LocalSocketAddress; 21 import android.os.Handler; 22 import android.os.HandlerThread; 23 import android.os.Message; 24 import android.os.SystemClock; 25 import android.util.Slog; 26 27 import java.io.IOException; 28 import java.io.InputStream; 29 import java.io.OutputStream; 30 import java.util.ArrayList; 31 import java.util.concurrent.BlockingQueue; 32 import java.util.concurrent.LinkedBlockingQueue; 33 34 /** 35 * Generic connector class for interfacing with a native 36 * daemon which uses the libsysutils FrameworkListener 37 * protocol. 38 */ 39 final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor { 40 private static final boolean LOCAL_LOGD = false; 41 42 private BlockingQueue<String> mResponseQueue; 43 private OutputStream mOutputStream; 44 private String TAG = "NativeDaemonConnector"; 45 private String mSocket; 46 private INativeDaemonConnectorCallbacks mCallbacks; 47 private Handler mCallbackHandler; 48 49 /** Lock held whenever communicating with native daemon. */ 50 private Object mDaemonLock = new Object(); 51 52 private final int BUFFER_SIZE = 4096; 53 54 class ResponseCode { 55 public static final int ActionInitiated = 100; 56 57 public static final int CommandOkay = 200; 58 59 // The range of 400 -> 599 is reserved for cmd failures 60 public static final int OperationFailed = 400; 61 public static final int CommandSyntaxError = 500; 62 public static final int CommandParameterError = 501; 63 64 public static final int UnsolicitedInformational = 600; 65 66 // 67 public static final int FailedRangeStart = 400; 68 public static final int FailedRangeEnd = 599; 69 } 70 71 NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, 72 String socket, int responseQueueSize, String logTag) { 73 mCallbacks = callbacks; 74 if (logTag != null) 75 TAG = logTag; 76 mSocket = socket; 77 mResponseQueue = new LinkedBlockingQueue<String>(responseQueueSize); 78 } 79 80 @Override 81 public void run() { 82 HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler"); 83 thread.start(); 84 mCallbackHandler = new Handler(thread.getLooper(), this); 85 86 while (true) { 87 try { 88 listenToSocket(); 89 } catch (Exception e) { 90 Slog.e(TAG, "Error in NativeDaemonConnector", e); 91 SystemClock.sleep(5000); 92 } 93 } 94 } 95 96 @Override 97 public boolean handleMessage(Message msg) { 98 String event = (String) msg.obj; 99 try { 100 if (!mCallbacks.onEvent(msg.what, event, event.split(" "))) { 101 Slog.w(TAG, String.format( 102 "Unhandled event '%s'", event)); 103 } 104 } catch (Exception e) { 105 Slog.e(TAG, String.format( 106 "Error handling '%s'", event), e); 107 } 108 return true; 109 } 110 111 private void listenToSocket() throws IOException { 112 LocalSocket socket = null; 113 114 try { 115 socket = new LocalSocket(); 116 LocalSocketAddress address = new LocalSocketAddress(mSocket, 117 LocalSocketAddress.Namespace.RESERVED); 118 119 socket.connect(address); 120 121 InputStream inputStream = socket.getInputStream(); 122 mOutputStream = socket.getOutputStream(); 123 124 mCallbacks.onDaemonConnected(); 125 126 byte[] buffer = new byte[BUFFER_SIZE]; 127 int start = 0; 128 129 while (true) { 130 int count = inputStream.read(buffer, start, BUFFER_SIZE - start); 131 if (count < 0) break; 132 133 // Add our starting point to the count and reset the start. 134 count += start; 135 start = 0; 136 137 for (int i = 0; i < count; i++) { 138 if (buffer[i] == 0) { 139 String event = new String(buffer, start, i - start); 140 if (LOCAL_LOGD) Slog.d(TAG, String.format("RCV <- {%s}", event)); 141 142 String[] tokens = event.split(" ", 2); 143 try { 144 int code = Integer.parseInt(tokens[0]); 145 146 if (code >= ResponseCode.UnsolicitedInformational) { 147 mCallbackHandler.sendMessage( 148 mCallbackHandler.obtainMessage(code, event)); 149 } else { 150 try { 151 mResponseQueue.put(event); 152 } catch (InterruptedException ex) { 153 Slog.e(TAG, "Failed to put response onto queue", ex); 154 } 155 } 156 } catch (NumberFormatException nfe) { 157 Slog.w(TAG, String.format("Bad msg (%s)", event)); 158 } 159 start = i + 1; 160 } 161 } 162 163 // We should end at the amount we read. If not, compact then 164 // buffer and read again. 165 if (start != count) { 166 final int remaining = BUFFER_SIZE - start; 167 System.arraycopy(buffer, start, buffer, 0, remaining); 168 start = remaining; 169 } else { 170 start = 0; 171 } 172 } 173 } catch (IOException ex) { 174 Slog.e(TAG, "Communications error", ex); 175 throw ex; 176 } finally { 177 synchronized (mDaemonLock) { 178 if (mOutputStream != null) { 179 try { 180 mOutputStream.close(); 181 } catch (IOException e) { 182 Slog.w(TAG, "Failed closing output stream", e); 183 } 184 mOutputStream = null; 185 } 186 } 187 188 try { 189 if (socket != null) { 190 socket.close(); 191 } 192 } catch (IOException ex) { 193 Slog.w(TAG, "Failed closing socket", ex); 194 } 195 } 196 } 197 198 private void sendCommandLocked(String command) throws NativeDaemonConnectorException { 199 sendCommandLocked(command, null); 200 } 201 202 /** 203 * Sends a command to the daemon with a single argument 204 * 205 * @param command The command to send to the daemon 206 * @param argument The argument to send with the command (or null) 207 */ 208 private void sendCommandLocked(String command, String argument) 209 throws NativeDaemonConnectorException { 210 if (command != null && command.indexOf('\0') >= 0) { 211 throw new IllegalArgumentException("unexpected command: " + command); 212 } 213 if (argument != null && argument.indexOf('\0') >= 0) { 214 throw new IllegalArgumentException("unexpected argument: " + argument); 215 } 216 217 if (LOCAL_LOGD) Slog.d(TAG, String.format("SND -> {%s} {%s}", command, argument)); 218 if (mOutputStream == null) { 219 Slog.e(TAG, "No connection to daemon", new IllegalStateException()); 220 throw new NativeDaemonConnectorException("No output stream!"); 221 } else { 222 StringBuilder builder = new StringBuilder(command); 223 if (argument != null) { 224 builder.append(argument); 225 } 226 builder.append('\0'); 227 228 try { 229 mOutputStream.write(builder.toString().getBytes()); 230 } catch (IOException ex) { 231 Slog.e(TAG, "IOException in sendCommand", ex); 232 } 233 } 234 } 235 236 /** 237 * Issue a command to the native daemon and return the responses 238 */ 239 public ArrayList<String> doCommand(String cmd) throws NativeDaemonConnectorException { 240 synchronized (mDaemonLock) { 241 return doCommandLocked(cmd); 242 } 243 } 244 245 private ArrayList<String> doCommandLocked(String cmd) throws NativeDaemonConnectorException { 246 mResponseQueue.clear(); 247 sendCommandLocked(cmd); 248 249 ArrayList<String> response = new ArrayList<String>(); 250 boolean complete = false; 251 int code = -1; 252 253 while (!complete) { 254 try { 255 // TODO - this should not block forever 256 String line = mResponseQueue.take(); 257 if (LOCAL_LOGD) Slog.d(TAG, String.format("RSP <- {%s}", line)); 258 String[] tokens = line.split(" "); 259 try { 260 code = Integer.parseInt(tokens[0]); 261 } catch (NumberFormatException nfe) { 262 throw new NativeDaemonConnectorException( 263 String.format("Invalid response from daemon (%s)", line)); 264 } 265 266 if ((code >= 200) && (code < 600)) { 267 complete = true; 268 } 269 response.add(line); 270 } catch (InterruptedException ex) { 271 Slog.e(TAG, "Failed to process response", ex); 272 } 273 } 274 275 if (code >= ResponseCode.FailedRangeStart && 276 code <= ResponseCode.FailedRangeEnd) { 277 /* 278 * Note: The format of the last response in this case is 279 * "NNN <errmsg>" 280 */ 281 throw new NativeDaemonConnectorException( 282 code, cmd, response.get(response.size()-1).substring(4)); 283 } 284 return response; 285 } 286 287 /** 288 * Issues a list command and returns the cooked list 289 */ 290 public String[] doListCommand(String cmd, int expectedResponseCode) 291 throws NativeDaemonConnectorException { 292 293 ArrayList<String> rsp = doCommand(cmd); 294 String[] rdata = new String[rsp.size()-1]; 295 int idx = 0; 296 297 for (int i = 0; i < rsp.size(); i++) { 298 String line = rsp.get(i); 299 try { 300 String[] tok = line.split(" "); 301 int code = Integer.parseInt(tok[0]); 302 if (code == expectedResponseCode) { 303 rdata[idx++] = line.substring(tok[0].length() + 1); 304 } else if (code == NativeDaemonConnector.ResponseCode.CommandOkay) { 305 if (LOCAL_LOGD) Slog.d(TAG, String.format("List terminated with {%s}", line)); 306 int last = rsp.size() -1; 307 if (i != last) { 308 Slog.w(TAG, String.format("Recv'd %d lines after end of list {%s}", (last-i), cmd)); 309 for (int j = i; j <= last ; j++) { 310 Slog.w(TAG, String.format("ExtraData <%s>", rsp.get(i))); 311 } 312 } 313 return rdata; 314 } else { 315 throw new NativeDaemonConnectorException( 316 String.format("Expected list response %d, but got %d", 317 expectedResponseCode, code)); 318 } 319 } catch (NumberFormatException nfe) { 320 throw new NativeDaemonConnectorException( 321 String.format("Error reading code '%s'", line)); 322 } 323 } 324 throw new NativeDaemonConnectorException("Got an empty response"); 325 } 326 327 /** {@inheritDoc} */ 328 public void monitor() { 329 synchronized (mDaemonLock) { } 330 } 331 } 332