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