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