blob: fed554cce31ca07d128bb46282847bf560fdc772 [file] [log] [blame]
San Mehat67bd2cd2010-01-12 12:18:49 -08001/*
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;
Chia-chi Yehe5750a32011-08-03 14:42:11 -070022import android.os.Handler;
23import android.os.HandlerThread;
24import android.os.Message;
San Mehat67bd2cd2010-01-12 12:18:49 -080025import android.os.SystemClock;
26import android.os.SystemProperties;
Joe Onorato8a9b2202010-02-26 18:56:32 -080027import android.util.Slog;
San Mehat67bd2cd2010-01-12 12:18:49 -080028
29import java.io.IOException;
30import java.io.InputStream;
31import java.io.OutputStream;
32import java.net.Socket;
San Mehat67bd2cd2010-01-12 12:18:49 -080033
34import java.util.List;
35import java.util.ArrayList;
36import java.util.ListIterator;
37import java.util.concurrent.BlockingQueue;
38import java.util.concurrent.LinkedBlockingQueue;
39
40/**
41 * Generic connector class for interfacing with a native
42 * daemon which uses the libsysutils FrameworkListener
43 * protocol.
44 */
Chia-chi Yehe5750a32011-08-03 14:42:11 -070045final class NativeDaemonConnector implements Runnable, Handler.Callback {
San Mehat1ff43712010-02-04 15:09:02 -080046 private static final boolean LOCAL_LOGD = false;
San Mehat67bd2cd2010-01-12 12:18:49 -080047
48 private BlockingQueue<String> mResponseQueue;
49 private OutputStream mOutputStream;
50 private String TAG = "NativeDaemonConnector";
51 private String mSocket;
52 private INativeDaemonConnectorCallbacks mCallbacks;
Chia-chi Yehe5750a32011-08-03 14:42:11 -070053 private Handler mCallbackHandler;
San Mehat67bd2cd2010-01-12 12:18:49 -080054
Kenny Root961aa8c2010-03-22 18:02:45 -070055 private final int BUFFER_SIZE = 4096;
56
San Mehat67bd2cd2010-01-12 12:18:49 -080057 class ResponseCode {
58 public static final int ActionInitiated = 100;
59
60 public static final int CommandOkay = 200;
61
62 // The range of 400 -> 599 is reserved for cmd failures
63 public static final int OperationFailed = 400;
64 public static final int CommandSyntaxError = 500;
65 public static final int CommandParameterError = 501;
66
67 public static final int UnsolicitedInformational = 600;
68
69 //
70 public static final int FailedRangeStart = 400;
71 public static final int FailedRangeEnd = 599;
72 }
73
74 NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks,
75 String socket, int responseQueueSize, String logTag) {
76 mCallbacks = callbacks;
77 if (logTag != null)
78 TAG = logTag;
79 mSocket = socket;
80 mResponseQueue = new LinkedBlockingQueue<String>(responseQueueSize);
81 }
82
Chia-chi Yehe5750a32011-08-03 14:42:11 -070083 @Override
San Mehat67bd2cd2010-01-12 12:18:49 -080084 public void run() {
Chia-chi Yehe5750a32011-08-03 14:42:11 -070085 HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
86 thread.start();
87 mCallbackHandler = new Handler(thread.getLooper(), this);
San Mehat67bd2cd2010-01-12 12:18:49 -080088
89 while (true) {
90 try {
91 listenToSocket();
92 } catch (Exception e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -080093 Slog.e(TAG, "Error in NativeDaemonConnector", e);
San Mehat4c27e0e2010-01-29 05:22:17 -080094 SystemClock.sleep(5000);
San Mehat67bd2cd2010-01-12 12:18:49 -080095 }
96 }
97 }
98
Chia-chi Yehe5750a32011-08-03 14:42:11 -070099 @Override
100 public boolean handleMessage(Message msg) {
101 String event = (String) msg.obj;
102 try {
103 if (!mCallbacks.onEvent(msg.what, event, event.split(" "))) {
104 Slog.w(TAG, String.format(
105 "Unhandled event '%s'", event));
106 }
107 } catch (Exception e) {
108 Slog.e(TAG, String.format(
109 "Error handling '%s'", event), e);
110 }
111 return true;
112 }
113
San Mehat4c27e0e2010-01-29 05:22:17 -0800114 private void listenToSocket() throws IOException {
Kenny Root961aa8c2010-03-22 18:02:45 -0700115 LocalSocket socket = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800116
117 try {
118 socket = new LocalSocket();
119 LocalSocketAddress address = new LocalSocketAddress(mSocket,
120 LocalSocketAddress.Namespace.RESERVED);
121
122 socket.connect(address);
San Mehat67bd2cd2010-01-12 12:18:49 -0800123
124 InputStream inputStream = socket.getInputStream();
125 mOutputStream = socket.getOutputStream();
126
anga030bc882011-02-01 14:10:25 +0100127 mCallbacks.onDaemonConnected();
128
Kenny Root961aa8c2010-03-22 18:02:45 -0700129 byte[] buffer = new byte[BUFFER_SIZE];
130 int start = 0;
San Mehat67bd2cd2010-01-12 12:18:49 -0800131
132 while (true) {
Kenny Root961aa8c2010-03-22 18:02:45 -0700133 int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
San Mehat67bd2cd2010-01-12 12:18:49 -0800134 if (count < 0) break;
135
Kenny Root12da9d72010-09-02 22:18:14 -0700136 // Add our starting point to the count and reset the start.
137 count += start;
138 start = 0;
139
San Mehat67bd2cd2010-01-12 12:18:49 -0800140 for (int i = 0; i < count; i++) {
141 if (buffer[i] == 0) {
142 String event = new String(buffer, start, i - start);
Joe Onorato8a9b2202010-02-26 18:56:32 -0800143 if (LOCAL_LOGD) Slog.d(TAG, String.format("RCV <- {%s}", event));
San Mehat67bd2cd2010-01-12 12:18:49 -0800144
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700145 String[] tokens = event.split(" ", 2);
San Mehat67bd2cd2010-01-12 12:18:49 -0800146 try {
147 int code = Integer.parseInt(tokens[0]);
148
149 if (code >= ResponseCode.UnsolicitedInformational) {
Chia-chi Yehe5750a32011-08-03 14:42:11 -0700150 mCallbackHandler.sendMessage(
151 mCallbackHandler.obtainMessage(code, event));
Irfan Sheriff1cd94ef2011-01-16 14:31:55 -0800152 } else {
153 try {
154 mResponseQueue.put(event);
155 } catch (InterruptedException ex) {
156 Slog.e(TAG, "Failed to put response onto queue", ex);
157 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800158 }
159 } catch (NumberFormatException nfe) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800160 Slog.w(TAG, String.format("Bad msg (%s)", event));
San Mehat67bd2cd2010-01-12 12:18:49 -0800161 }
162 start = i + 1;
163 }
164 }
Kenny Root12da9d72010-09-02 22:18:14 -0700165
166 // We should end at the amount we read. If not, compact then
167 // buffer and read again.
Kenny Root961aa8c2010-03-22 18:02:45 -0700168 if (start != count) {
169 final int remaining = BUFFER_SIZE - start;
170 System.arraycopy(buffer, start, buffer, 0, remaining);
171 start = remaining;
172 } else {
173 start = 0;
174 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800175 }
176 } catch (IOException ex) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800177 Slog.e(TAG, "Communications error", ex);
San Mehat4c27e0e2010-01-29 05:22:17 -0800178 throw ex;
179 } finally {
180 synchronized (this) {
181 if (mOutputStream != null) {
182 try {
183 mOutputStream.close();
184 } catch (IOException e) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800185 Slog.w(TAG, "Failed closing output stream", e);
San Mehat4c27e0e2010-01-29 05:22:17 -0800186 }
187 mOutputStream = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800188 }
San Mehat4c27e0e2010-01-29 05:22:17 -0800189 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800190
San Mehat4c27e0e2010-01-29 05:22:17 -0800191 try {
192 if (socket != null) {
193 socket.close();
194 }
195 } catch (IOException ex) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800196 Slog.w(TAG, "Failed closing socket", ex);
San Mehat67bd2cd2010-01-12 12:18:49 -0800197 }
198 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800199 }
200
Robert Greenwalte5c3afb2010-09-22 14:32:35 -0700201 private void sendCommand(String command)
202 throws NativeDaemonConnectorException {
San Mehat67bd2cd2010-01-12 12:18:49 -0800203 sendCommand(command, null);
204 }
205
206 /**
207 * Sends a command to the daemon with a single argument
208 *
209 * @param command The command to send to the daemon
210 * @param argument The argument to send with the command (or null)
211 */
Robert Greenwalte5c3afb2010-09-22 14:32:35 -0700212 private void sendCommand(String command, String argument)
213 throws NativeDaemonConnectorException {
San Mehat67bd2cd2010-01-12 12:18:49 -0800214 synchronized (this) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800215 if (LOCAL_LOGD) Slog.d(TAG, String.format("SND -> {%s} {%s}", command, argument));
San Mehat67bd2cd2010-01-12 12:18:49 -0800216 if (mOutputStream == null) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800217 Slog.e(TAG, "No connection to daemon", new IllegalStateException());
Robert Greenwalte5c3afb2010-09-22 14:32:35 -0700218 throw new NativeDaemonConnectorException("No output stream!");
San Mehat67bd2cd2010-01-12 12:18:49 -0800219 } else {
220 StringBuilder builder = new StringBuilder(command);
221 if (argument != null) {
222 builder.append(argument);
223 }
224 builder.append('\0');
225
226 try {
227 mOutputStream.write(builder.toString().getBytes());
228 } catch (IOException ex) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800229 Slog.e(TAG, "IOException in sendCommand", ex);
San Mehat67bd2cd2010-01-12 12:18:49 -0800230 }
231 }
232 }
233 }
234
San Mehatdeba6932010-01-20 15:14:31 -0800235 /**
236 * Issue a command to the native daemon and return the responses
237 */
San Mehat4c27e0e2010-01-29 05:22:17 -0800238 public synchronized ArrayList<String> doCommand(String cmd)
239 throws NativeDaemonConnectorException {
Irfan Sheriff1cd94ef2011-01-16 14:31:55 -0800240 mResponseQueue.clear();
San Mehat67bd2cd2010-01-12 12:18:49 -0800241 sendCommand(cmd);
242
243 ArrayList<String> response = new ArrayList<String>();
244 boolean complete = false;
245 int code = -1;
246
247 while (!complete) {
248 try {
Robert Greenwalte5c3afb2010-09-22 14:32:35 -0700249 // TODO - this should not block forever
San Mehat67bd2cd2010-01-12 12:18:49 -0800250 String line = mResponseQueue.take();
Joe Onorato8a9b2202010-02-26 18:56:32 -0800251 if (LOCAL_LOGD) Slog.d(TAG, String.format("RSP <- {%s}", line));
San Mehat67bd2cd2010-01-12 12:18:49 -0800252 String[] tokens = line.split(" ");
253 try {
254 code = Integer.parseInt(tokens[0]);
255 } catch (NumberFormatException nfe) {
San Mehat4c27e0e2010-01-29 05:22:17 -0800256 throw new NativeDaemonConnectorException(
San Mehat67bd2cd2010-01-12 12:18:49 -0800257 String.format("Invalid response from daemon (%s)", line));
258 }
259
San Mehatec4caa02010-02-03 10:48:21 -0800260 if ((code >= 200) && (code < 600)) {
San Mehat67bd2cd2010-01-12 12:18:49 -0800261 complete = true;
San Mehatec4caa02010-02-03 10:48:21 -0800262 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800263 response.add(line);
264 } catch (InterruptedException ex) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800265 Slog.e(TAG, "Failed to process response", ex);
San Mehat67bd2cd2010-01-12 12:18:49 -0800266 }
267 }
268
269 if (code >= ResponseCode.FailedRangeStart &&
270 code <= ResponseCode.FailedRangeEnd) {
San Mehatec4caa02010-02-03 10:48:21 -0800271 /*
272 * Note: The format of the last response in this case is
273 * "NNN <errmsg>"
274 */
275 throw new NativeDaemonConnectorException(
276 code, cmd, response.get(response.size()-1).substring(4));
San Mehat67bd2cd2010-01-12 12:18:49 -0800277 }
278 return response;
279 }
San Mehatdeba6932010-01-20 15:14:31 -0800280
281 /*
282 * Issues a list command and returns the cooked list
283 */
284 public String[] doListCommand(String cmd, int expectedResponseCode)
San Mehat4c27e0e2010-01-29 05:22:17 -0800285 throws NativeDaemonConnectorException {
San Mehatdeba6932010-01-20 15:14:31 -0800286
287 ArrayList<String> rsp = doCommand(cmd);
288 String[] rdata = new String[rsp.size()-1];
289 int idx = 0;
290
San Mehat1ff43712010-02-04 15:09:02 -0800291 for (int i = 0; i < rsp.size(); i++) {
292 String line = rsp.get(i);
San Mehatdeba6932010-01-20 15:14:31 -0800293 try {
294 String[] tok = line.split(" ");
295 int code = Integer.parseInt(tok[0]);
296 if (code == expectedResponseCode) {
San Mehat80120b42010-01-26 12:48:39 -0800297 rdata[idx++] = line.substring(tok[0].length() + 1);
San Mehatdeba6932010-01-20 15:14:31 -0800298 } else if (code == NativeDaemonConnector.ResponseCode.CommandOkay) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800299 if (LOCAL_LOGD) Slog.d(TAG, String.format("List terminated with {%s}", line));
San Mehat4086f752010-02-17 09:03:29 -0800300 int last = rsp.size() -1;
301 if (i != last) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800302 Slog.w(TAG, String.format("Recv'd %d lines after end of list {%s}", (last-i), cmd));
San Mehat4086f752010-02-17 09:03:29 -0800303 for (int j = i; j <= last ; j++) {
Joe Onorato8a9b2202010-02-26 18:56:32 -0800304 Slog.w(TAG, String.format("ExtraData <%s>", rsp.get(i)));
San Mehat4086f752010-02-17 09:03:29 -0800305 }
San Mehat1ff43712010-02-04 15:09:02 -0800306 }
San Mehatdeba6932010-01-20 15:14:31 -0800307 return rdata;
308 } else {
San Mehat4c27e0e2010-01-29 05:22:17 -0800309 throw new NativeDaemonConnectorException(
San Mehatdeba6932010-01-20 15:14:31 -0800310 String.format("Expected list response %d, but got %d",
311 expectedResponseCode, code));
312 }
313 } catch (NumberFormatException nfe) {
San Mehat4c27e0e2010-01-29 05:22:17 -0800314 throw new NativeDaemonConnectorException(
315 String.format("Error reading code '%s'", line));
San Mehatdeba6932010-01-20 15:14:31 -0800316 }
317 }
San Mehat4c27e0e2010-01-29 05:22:17 -0800318 throw new NativeDaemonConnectorException("Got an empty response");
San Mehatdeba6932010-01-20 15:14:31 -0800319 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800320}