NativeDaemonConnector.java revision 961aa8c8879e9f68c0eddcaf87565200a4347134
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 for (int i = 0; i < count; i++) { 113 if (buffer[i] == 0) { 114 String event = new String(buffer, start, i - start); 115 if (LOCAL_LOGD) Slog.d(TAG, String.format("RCV <- {%s}", event)); 116 117 String[] tokens = event.split(" "); 118 try { 119 int code = Integer.parseInt(tokens[0]); 120 121 if (code >= ResponseCode.UnsolicitedInformational) { 122 try { 123 if (!mCallbacks.onEvent(code, event, tokens)) { 124 Slog.w(TAG, String.format( 125 "Unhandled event (%s)", event)); 126 } 127 } catch (Exception ex) { 128 Slog.e(TAG, String.format( 129 "Error handling '%s'", event), ex); 130 } 131 } else { 132 try { 133 mResponseQueue.put(event); 134 } catch (InterruptedException ex) { 135 Slog.e(TAG, "Failed to put response onto queue", ex); 136 } 137 } 138 } catch (NumberFormatException nfe) { 139 Slog.w(TAG, String.format("Bad msg (%s)", event)); 140 } 141 start = i + 1; 142 } 143 } 144 if (start != count) { 145 final int remaining = BUFFER_SIZE - start; 146 System.arraycopy(buffer, start, buffer, 0, remaining); 147 start = remaining; 148 } else { 149 start = 0; 150 } 151 } 152 } catch (IOException ex) { 153 Slog.e(TAG, "Communications error", ex); 154 throw ex; 155 } finally { 156 synchronized (this) { 157 if (mOutputStream != null) { 158 try { 159 mOutputStream.close(); 160 } catch (IOException e) { 161 Slog.w(TAG, "Failed closing output stream", e); 162 } 163 mOutputStream = null; 164 } 165 } 166 167 try { 168 if (socket != null) { 169 socket.close(); 170 } 171 } catch (IOException ex) { 172 Slog.w(TAG, "Failed closing socket", ex); 173 } 174 } 175 } 176 177 private void sendCommand(String command) { 178 sendCommand(command, null); 179 } 180 181 /** 182 * Sends a command to the daemon with a single argument 183 * 184 * @param command The command to send to the daemon 185 * @param argument The argument to send with the command (or null) 186 */ 187 private void sendCommand(String command, String argument) { 188 synchronized (this) { 189 if (LOCAL_LOGD) Slog.d(TAG, String.format("SND -> {%s} {%s}", command, argument)); 190 if (mOutputStream == null) { 191 Slog.e(TAG, "No connection to daemon", new IllegalStateException()); 192 } else { 193 StringBuilder builder = new StringBuilder(command); 194 if (argument != null) { 195 builder.append(argument); 196 } 197 builder.append('\0'); 198 199 try { 200 mOutputStream.write(builder.toString().getBytes()); 201 } catch (IOException ex) { 202 Slog.e(TAG, "IOException in sendCommand", ex); 203 } 204 } 205 } 206 } 207 208 /** 209 * Issue a command to the native daemon and return the responses 210 */ 211 public synchronized ArrayList<String> doCommand(String cmd) 212 throws NativeDaemonConnectorException { 213 sendCommand(cmd); 214 215 ArrayList<String> response = new ArrayList<String>(); 216 boolean complete = false; 217 int code = -1; 218 219 while (!complete) { 220 try { 221 String line = mResponseQueue.take(); 222 if (LOCAL_LOGD) Slog.d(TAG, String.format("RSP <- {%s}", line)); 223 String[] tokens = line.split(" "); 224 try { 225 code = Integer.parseInt(tokens[0]); 226 } catch (NumberFormatException nfe) { 227 throw new NativeDaemonConnectorException( 228 String.format("Invalid response from daemon (%s)", line)); 229 } 230 231 if ((code >= 200) && (code < 600)) { 232 complete = true; 233 } 234 response.add(line); 235 } catch (InterruptedException ex) { 236 Slog.e(TAG, "Failed to process response", ex); 237 } 238 } 239 240 if (code >= ResponseCode.FailedRangeStart && 241 code <= ResponseCode.FailedRangeEnd) { 242 /* 243 * Note: The format of the last response in this case is 244 * "NNN <errmsg>" 245 */ 246 throw new NativeDaemonConnectorException( 247 code, cmd, response.get(response.size()-1).substring(4)); 248 } 249 return response; 250 } 251 252 /* 253 * Issues a list command and returns the cooked list 254 */ 255 public String[] doListCommand(String cmd, int expectedResponseCode) 256 throws NativeDaemonConnectorException { 257 258 ArrayList<String> rsp = doCommand(cmd); 259 String[] rdata = new String[rsp.size()-1]; 260 int idx = 0; 261 262 for (int i = 0; i < rsp.size(); i++) { 263 String line = rsp.get(i); 264 try { 265 String[] tok = line.split(" "); 266 int code = Integer.parseInt(tok[0]); 267 if (code == expectedResponseCode) { 268 rdata[idx++] = line.substring(tok[0].length() + 1); 269 } else if (code == NativeDaemonConnector.ResponseCode.CommandOkay) { 270 if (LOCAL_LOGD) Slog.d(TAG, String.format("List terminated with {%s}", line)); 271 int last = rsp.size() -1; 272 if (i != last) { 273 Slog.w(TAG, String.format("Recv'd %d lines after end of list {%s}", (last-i), cmd)); 274 for (int j = i; j <= last ; j++) { 275 Slog.w(TAG, String.format("ExtraData <%s>", rsp.get(i))); 276 } 277 } 278 return rdata; 279 } else { 280 throw new NativeDaemonConnectorException( 281 String.format("Expected list response %d, but got %d", 282 expectedResponseCode, code)); 283 } 284 } catch (NumberFormatException nfe) { 285 throw new NativeDaemonConnectorException( 286 String.format("Error reading code '%s'", line)); 287 } 288 } 289 throw new NativeDaemonConnectorException("Got an empty response"); 290 } 291} 292