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