167bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat/*
267bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat * Copyright (C) 2007 The Android Open Source Project
367bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat *
467bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat * Licensed under the Apache License, Version 2.0 (the "License");
567bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat * you may not use this file except in compliance with the License.
667bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat * You may obtain a copy of the License at
767bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat *
867bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat *      http://www.apache.org/licenses/LICENSE-2.0
967bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat *
1067bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat * Unless required by applicable law or agreed to in writing, software
1167bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat * distributed under the License is distributed on an "AS IS" BASIS,
1267bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1367bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat * See the License for the specific language governing permissions and
1467bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat * limitations under the License.
1567bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat */
1667bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
1767bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehatpackage com.android.server;
1867bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
1967bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehatimport android.net.LocalSocket;
20fa23c5ae226c1a1d39f89c5c87d4f340e91d90e0Jeff Sharkeyimport android.net.LocalSocketAddress;
217421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colittiimport android.os.Build;
22e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yehimport android.os.Handler;
23e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yehimport android.os.Message;
2467bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehatimport android.os.SystemClock;
25470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwaltimport android.util.LocalLog;
268a9b22056b13477f59df934928c00c58b5871c95Joe Onoratoimport android.util.Slog;
2767bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
288b2c3a14603d163d7564e6f60286995079687690Jeff Sharkeyimport com.android.internal.annotations.VisibleForTesting;
2931c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkeyimport com.google.android.collect.Lists;
3031c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
31470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwaltimport java.io.FileDescriptor;
3267bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehatimport java.io.IOException;
3367bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehatimport java.io.InputStream;
3467bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehatimport java.io.OutputStream;
35470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwaltimport java.io.PrintWriter;
36d396a448b2e36e29598c954b64bfddef73f3fae0Elliott Hughesimport java.nio.charset.StandardCharsets;
3767bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehatimport java.util.ArrayList;
38470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwaltimport java.util.concurrent.atomic.AtomicInteger;
39ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwaltimport java.util.concurrent.ArrayBlockingQueue;
40ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwaltimport java.util.concurrent.BlockingQueue;
41ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwaltimport java.util.concurrent.TimeUnit;
42470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwaltimport java.util.LinkedList;
4367bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
4467bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat/**
4531c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey * Generic connector class for interfacing with a native daemon which uses the
4631c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey * {@code libsysutils} FrameworkListener protocol.
4767bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat */
48fa23c5ae226c1a1d39f89c5c87d4f340e91d90e0Jeff Sharkeyfinal class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
4931c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    private static final boolean LOGD = false;
5067bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
5131c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    private final String TAG;
5267bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
5331c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    private String mSocket;
5431c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    private OutputStream mOutputStream;
55470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt    private LocalLog mLocalLog;
56961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root
57470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt    private final ResponseQueue mResponseQueue;
5867bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
5931c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    private INativeDaemonConnectorCallbacks mCallbacks;
6031c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    private Handler mCallbackHandler;
6167bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
62470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt    private AtomicInteger mSequenceNumber;
63470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt
64470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt    private static final int DEFAULT_TIMEOUT = 1 * 60 * 1000; /* 1 minute */
655a0c320a820a0ccaafaa87ad858a29bf5d88a8b9Robert Greenwalt    private static final long WARN_EXECUTE_DELAY_MS = 500; /* .5 sec */
66470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt
6731c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    /** Lock held whenever communicating with native daemon. */
6831c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    private final Object mDaemonLock = new Object();
6967bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
7031c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    private final int BUFFER_SIZE = 4096;
7167bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
7231c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
73470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt            int responseQueueSize, String logTag, int maxLogSize) {
7467bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat        mCallbacks = callbacks;
7567bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat        mSocket = socket;
76470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        mResponseQueue = new ResponseQueue(responseQueueSize);
77470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        mSequenceNumber = new AtomicInteger(0);
7831c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        TAG = logTag != null ? logTag : "NativeDaemonConnector";
79470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt        mLocalLog = new LocalLog(maxLogSize);
8067bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat    }
8167bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
82e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yeh    @Override
8367bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat    public void run() {
848d044e8bc287c1a567d82aedbe30085b011544c3Dianne Hackborn        mCallbackHandler = new Handler(FgThread.get().getLooper(), this);
8567bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
8667bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat        while (true) {
8767bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat            try {
8867bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat                listenToSocket();
8967bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat            } catch (Exception e) {
90470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                loge("Error in NativeDaemonConnector: " + e);
914c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat                SystemClock.sleep(5000);
9267bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat            }
9367bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat        }
9467bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat    }
9567bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
96e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yeh    @Override
97e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yeh    public boolean handleMessage(Message msg) {
98e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yeh        String event = (String) msg.obj;
99e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yeh        try {
1002d34b4a88531e51982b030c43d672ec2cc3d8f36Robert Greenwalt            if (!mCallbacks.onEvent(msg.what, event, NativeDaemonEvent.unescapeArgs(event))) {
101470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                log(String.format("Unhandled event '%s'", event));
102e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yeh            }
103e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yeh        } catch (Exception e) {
104470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt            loge("Error handling '" + event + "': " + e);
105e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yeh        }
106e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yeh        return true;
107e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yeh    }
108e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yeh
1097421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti    private LocalSocketAddress determineSocketAddress() {
1107421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti        // If we're testing, set up a socket in a namespace that's accessible to test code.
1117421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti        // In order to ensure that unprivileged apps aren't able to impersonate native daemons on
1127421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti        // production devices, even if said native daemons ill-advisedly pick a socket name that
1137421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti        // starts with __test__, only allow this on debug builds.
1147421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti        if (mSocket.startsWith("__test__") && Build.IS_DEBUGGABLE) {
1157421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti            return new LocalSocketAddress(mSocket);
1167421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti        } else {
1177421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti            return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
1187421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti        }
1197421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti    }
1207421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti
1214c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat    private void listenToSocket() throws IOException {
122961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root        LocalSocket socket = null;
12367bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
12467bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat        try {
12567bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat            socket = new LocalSocket();
1267421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti            LocalSocketAddress address = determineSocketAddress();
12767bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
12867bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat            socket.connect(address);
12967bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
13067bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat            InputStream inputStream = socket.getInputStream();
131470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt            synchronized (mDaemonLock) {
132470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                mOutputStream = socket.getOutputStream();
133470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt            }
13467bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
135030bc88b4c09a41b0d7dbe249aec55f33d6b8b8aanga            mCallbacks.onDaemonConnected();
136030bc88b4c09a41b0d7dbe249aec55f33d6b8b8aanga
137961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root            byte[] buffer = new byte[BUFFER_SIZE];
138961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root            int start = 0;
13967bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
14067bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat            while (true) {
141961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root                int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
142470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                if (count < 0) {
143470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                    loge("got " + count + " reading with start = " + start);
144470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                    break;
145470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                }
14667bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
14712da9d7472ae87b841575d5358e19f143d12f900Kenny Root                // Add our starting point to the count and reset the start.
14812da9d7472ae87b841575d5358e19f143d12f900Kenny Root                count += start;
14912da9d7472ae87b841575d5358e19f143d12f900Kenny Root                start = 0;
15012da9d7472ae87b841575d5358e19f143d12f900Kenny Root
15167bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat                for (int i = 0; i < count; i++) {
15267bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat                    if (buffer[i] == 0) {
153ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey                        final String rawEvent = new String(
154d396a448b2e36e29598c954b64bfddef73f3fae0Elliott Hughes                                buffer, start, i - start, StandardCharsets.UTF_8);
155470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt                        log("RCV <- {" + rawEvent + "}");
15667bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
15767bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat                        try {
15831c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey                            final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(
15931c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey                                    rawEvent);
16031c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey                            if (event.isClassUnsolicited()) {
161ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey                                // TODO: migrate to sending NativeDaemonEvent instances
16231c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey                                mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
16331c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey                                        event.getCode(), event.getRawEvent()));
1641cd94ef9570a1534e32b27d5b174dc690c9be6b9Irfan Sheriff                            } else {
165470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                                mResponseQueue.add(event.getCmdNumber(), event);
16667bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat                            }
16731c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey                        } catch (IllegalArgumentException e) {
168470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                            log("Problem parsing message: " + rawEvent + " - " + e);
16967bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat                        }
17031c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
17167bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat                        start = i + 1;
17267bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat                    }
17367bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat                }
174f0be1d89bf1cf5592ea1786d837f4f2329bdf66dRobert Greenwalt                if (start == 0) {
175d396a448b2e36e29598c954b64bfddef73f3fae0Elliott Hughes                    final String rawEvent = new String(buffer, start, count, StandardCharsets.UTF_8);
176f0be1d89bf1cf5592ea1786d837f4f2329bdf66dRobert Greenwalt                    log("RCV incomplete <- {" + rawEvent + "}");
177f0be1d89bf1cf5592ea1786d837f4f2329bdf66dRobert Greenwalt                }
17812da9d7472ae87b841575d5358e19f143d12f900Kenny Root
17912da9d7472ae87b841575d5358e19f143d12f900Kenny Root                // We should end at the amount we read. If not, compact then
18012da9d7472ae87b841575d5358e19f143d12f900Kenny Root                // buffer and read again.
181961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root                if (start != count) {
182961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root                    final int remaining = BUFFER_SIZE - start;
183961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root                    System.arraycopy(buffer, start, buffer, 0, remaining);
184961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root                    start = remaining;
185961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root                } else {
186961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root                    start = 0;
187961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root                }
18867bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat            }
18967bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat        } catch (IOException ex) {
190470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt            loge("Communications error: " + ex);
1914c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat            throw ex;
1924c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat        } finally {
193fa23c5ae226c1a1d39f89c5c87d4f340e91d90e0Jeff Sharkey            synchronized (mDaemonLock) {
1944c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat                if (mOutputStream != null) {
1954c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat                    try {
196470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                        loge("closing stream for " + mSocket);
1974c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat                        mOutputStream.close();
1984c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat                    } catch (IOException e) {
199470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                        loge("Failed closing output stream: " + e);
2004c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat                    }
2014c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat                    mOutputStream = null;
20267bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat                }
20367bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat            }
20467bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
2054c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat            try {
2064c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat                if (socket != null) {
2074c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat                    socket.close();
2084c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat                }
2094c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat            } catch (IOException ex) {
210470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                loge("Failed closing socket: " + ex);
21167bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat            }
21267bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat        }
21367bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat    }
21467bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
21567bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat    /**
21656cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey     * Wrapper around argument that indicates it's sensitive and shouldn't be
21756cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey     * logged.
21856cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey     */
21956cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey    public static class SensitiveArg {
22056cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        private final Object mArg;
22156cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey
22256cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        public SensitiveArg(Object arg) {
22356cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            mArg = arg;
22456cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        }
22556cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey
22656cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        @Override
22756cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        public String toString() {
22856cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            return String.valueOf(mArg);
22956cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        }
23056cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey    }
23156cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey
23256cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey    /**
233470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt     * Make command for daemon, escaping arguments as needed.
23467bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat     */
23556cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey    @VisibleForTesting
23656cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey    static void makeCommand(StringBuilder rawBuilder, StringBuilder logBuilder, int sequenceNumber,
23756cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            String cmd, Object... args) {
23831c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        if (cmd.indexOf('\0') >= 0) {
2397b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey            throw new IllegalArgumentException("Unexpected command: " + cmd);
2407b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey        }
2417b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey        if (cmd.indexOf(' ') >= 0) {
2427b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey            throw new IllegalArgumentException("Arguments must be separate from command");
243b0aec07f7462ff7563835c3107f4b46a28eae7a4Jeff Sharkey        }
24431c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
24556cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        rawBuilder.append(sequenceNumber).append(' ').append(cmd);
24656cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        logBuilder.append(sequenceNumber).append(' ').append(cmd);
24731c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        for (Object arg : args) {
24831c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            final String argString = String.valueOf(arg);
24931c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            if (argString.indexOf('\0') >= 0) {
2507b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey                throw new IllegalArgumentException("Unexpected argument: " + arg);
25131c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            }
25231c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
25356cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            rawBuilder.append(' ');
25456cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            logBuilder.append(' ');
25556cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey
25656cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            appendEscaped(rawBuilder, argString);
25756cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            if (arg instanceof SensitiveArg) {
25856cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey                logBuilder.append("[scrubbed]");
25956cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            } else {
26056cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey                appendEscaped(logBuilder, argString);
26156cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            }
262b0aec07f7462ff7563835c3107f4b46a28eae7a4Jeff Sharkey        }
26356cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey
26456cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        rawBuilder.append('\0');
26567bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat    }
26667bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
267deba6935c6595c724416cde3368a92c32d8f8683San Mehat    /**
268ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * Issue the given command to the native daemon and return a single expected
269ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * response.
270ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *
271ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * @throws NativeDaemonConnectorException when problem communicating with
272ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             native daemon, or if the response matches
273ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             {@link NativeDaemonEvent#isClassClientError()} or
274ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             {@link NativeDaemonEvent#isClassServerError()}.
275deba6935c6595c724416cde3368a92c32d8f8683San Mehat     */
276ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
277ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        return execute(cmd.mCmd, cmd.mArguments.toArray());
278ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    }
279ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey
280ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    /**
281ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * Issue the given command to the native daemon and return a single expected
2827b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey     * response. Any arguments must be separated from base command so they can
2837b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey     * be properly escaped.
284ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *
285ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * @throws NativeDaemonConnectorException when problem communicating with
286ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             native daemon, or if the response matches
287ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             {@link NativeDaemonEvent#isClassClientError()} or
288ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             {@link NativeDaemonEvent#isClassServerError()}.
289ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     */
290ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    public NativeDaemonEvent execute(String cmd, Object... args)
291ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey            throws NativeDaemonConnectorException {
292ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        final NativeDaemonEvent[] events = executeForList(cmd, args);
293ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        if (events.length != 1) {
294ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey            throw new NativeDaemonConnectorException(
295ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey                    "Expected exactly one response, but received " + events.length);
296ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        }
297ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        return events[0];
298ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    }
299ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey
300ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    /**
301ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * Issue the given command to the native daemon and return any
302ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * {@link NativeDaemonEvent#isClassContinue()} responses, including the
303ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * final terminal response.
304ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *
305ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * @throws NativeDaemonConnectorException when problem communicating with
306ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             native daemon, or if the response matches
307ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             {@link NativeDaemonEvent#isClassClientError()} or
308ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             {@link NativeDaemonEvent#isClassServerError()}.
309ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     */
310ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException {
311ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        return executeForList(cmd.mCmd, cmd.mArguments.toArray());
312ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    }
313ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey
314ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    /**
315ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * Issue the given command to the native daemon and return any
316ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * {@link NativeDaemonEvent#isClassContinue()} responses, including the
3177b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey     * final terminal response. Any arguments must be separated from base
3187b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey     * command so they can be properly escaped.
319ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *
320ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * @throws NativeDaemonConnectorException when problem communicating with
321ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             native daemon, or if the response matches
322ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             {@link NativeDaemonEvent#isClassClientError()} or
323ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             {@link NativeDaemonEvent#isClassServerError()}.
324ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     */
325ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    public NativeDaemonEvent[] executeForList(String cmd, Object... args)
32631c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            throws NativeDaemonConnectorException {
327470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt            return execute(DEFAULT_TIMEOUT, cmd, args);
328fa23c5ae226c1a1d39f89c5c87d4f340e91d90e0Jeff Sharkey    }
329fa23c5ae226c1a1d39f89c5c87d4f340e91d90e0Jeff Sharkey
330470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt    /**
3317b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey     * Issue the given command to the native daemon and return any {@linke
3327b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey     * NativeDaemonEvent@isClassContinue()} responses, including the final
3337b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey     * terminal response. Note that the timeout does not count time in deep
3347b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey     * sleep. Any arguments must be separated from base command so they can be
3357b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey     * properly escaped.
336470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt     *
337470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt     * @throws NativeDaemonConnectorException when problem communicating with
338470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt     *             native daemon, or if the response matches
339470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt     *             {@link NativeDaemonEvent#isClassClientError()} or
340470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt     *             {@link NativeDaemonEvent#isClassServerError()}.
341470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt     */
342470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt    public NativeDaemonEvent[] execute(int timeout, String cmd, Object... args)
34331c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            throws NativeDaemonConnectorException {
34456cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        final long startTime = SystemClock.elapsedRealtime();
34556cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey
34631c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
347d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt
34856cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        final StringBuilder rawBuilder = new StringBuilder();
34956cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        final StringBuilder logBuilder = new StringBuilder();
350d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt        final int sequenceNumber = mSequenceNumber.incrementAndGet();
351d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt
35256cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
353d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt
35456cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        final String rawCmd = rawBuilder.toString();
35556cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        final String logCmd = logBuilder.toString();
356d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt
35756cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        log("SND -> {" + logCmd + "}");
358d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt
359d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt        synchronized (mDaemonLock) {
360d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt            if (mOutputStream == null) {
361d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt                throw new NativeDaemonConnectorException("missing output stream");
362d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt            } else {
363d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt                try {
364b8292830f79fc76ffb9a1be5cd316212ac494d03Elliott Hughes                    mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
365d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt                } catch (IOException e) {
366d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt                    throw new NativeDaemonConnectorException("problem sending command", e);
367d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt                }
368d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt            }
369d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt        }
37067bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
37131c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        NativeDaemonEvent event = null;
37231c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        do {
37356cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            event = mResponseQueue.remove(sequenceNumber, timeout, logCmd);
374470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt            if (event == null) {
375d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt                loge("timed-out waiting for response to " + logCmd);
376d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt                throw new NativeDaemonFailureException(logCmd, event);
37767bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat            }
378b5aff3fde346225fab0f2751d4fb51b92bb73b32Robert Greenwalt            log("RMV <- {" + event + "}");
37931c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            events.add(event);
38031c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        } while (event.isClassContinue());
38131c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
3825a0c320a820a0ccaafaa87ad858a29bf5d88a8b9Robert Greenwalt        final long endTime = SystemClock.elapsedRealtime();
3835a0c320a820a0ccaafaa87ad858a29bf5d88a8b9Robert Greenwalt        if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
3845a0c320a820a0ccaafaa87ad858a29bf5d88a8b9Robert Greenwalt            loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
3855a0c320a820a0ccaafaa87ad858a29bf5d88a8b9Robert Greenwalt        }
3865a0c320a820a0ccaafaa87ad858a29bf5d88a8b9Robert Greenwalt
38731c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        if (event.isClassClientError()) {
388d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt            throw new NativeDaemonArgumentException(logCmd, event);
38931c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        }
39031c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        if (event.isClassServerError()) {
391d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt            throw new NativeDaemonFailureException(logCmd, event);
39267bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat        }
39367bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
39431c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        return events.toArray(new NativeDaemonEvent[events.size()]);
39531c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    }
39631c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
39731c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    /**
39831c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey     * Append the given argument to {@link StringBuilder}, escaping as needed,
39931c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey     * and surrounding with quotes when it contains spaces.
40031c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey     */
4018b2c3a14603d163d7564e6f60286995079687690Jeff Sharkey    @VisibleForTesting
40231c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    static void appendEscaped(StringBuilder builder, String arg) {
40331c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        final boolean hasSpaces = arg.indexOf(' ') >= 0;
40431c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        if (hasSpaces) {
40531c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            builder.append('"');
40631c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        }
40731c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
40831c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        final int length = arg.length();
40931c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        for (int i = 0; i < length; i++) {
41031c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            final char c = arg.charAt(i);
41131c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
41231c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            if (c == '"') {
41331c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey                builder.append("\\\"");
41431c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            } else if (c == '\\') {
41531c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey                builder.append("\\\\");
41631c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            } else {
41731c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey                builder.append(c);
418deba6935c6595c724416cde3368a92c32d8f8683San Mehat            }
419deba6935c6595c724416cde3368a92c32d8f8683San Mehat        }
42031c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
42131c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        if (hasSpaces) {
42231c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            builder.append('"');
42331c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        }
42431c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    }
42531c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
42631c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
42731c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
42831c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            super(command, event);
42931c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        }
43031c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
43131c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        @Override
43231c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        public IllegalArgumentException rethrowAsParcelableException() {
43331c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            throw new IllegalArgumentException(getMessage(), this);
43431c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        }
43531c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    }
43631c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
43731c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
43831c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
43931c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            super(command, event);
44031c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        }
441deba6935c6595c724416cde3368a92c32d8f8683San Mehat    }
442fa23c5ae226c1a1d39f89c5c87d4f340e91d90e0Jeff Sharkey
443ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    /**
4447b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey     * Command builder that handles argument list building. Any arguments must
4457b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey     * be separated from base command so they can be properly escaped.
446ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     */
447ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    public static class Command {
448ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        private String mCmd;
449ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        private ArrayList<Object> mArguments = Lists.newArrayList();
450ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey
451ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        public Command(String cmd, Object... args) {
452ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey            mCmd = cmd;
453ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey            for (Object arg : args) {
454ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey                appendArg(arg);
455ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey            }
456ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        }
457ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey
458ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        public Command appendArg(Object arg) {
459ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey            mArguments.add(arg);
460ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey            return this;
461ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        }
462ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    }
463ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey
464fa23c5ae226c1a1d39f89c5c87d4f340e91d90e0Jeff Sharkey    /** {@inheritDoc} */
465fa23c5ae226c1a1d39f89c5c87d4f340e91d90e0Jeff Sharkey    public void monitor() {
466fa23c5ae226c1a1d39f89c5c87d4f340e91d90e0Jeff Sharkey        synchronized (mDaemonLock) { }
467fa23c5ae226c1a1d39f89c5c87d4f340e91d90e0Jeff Sharkey    }
468470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt
469470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
470470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt        mLocalLog.dump(fd, pw, args);
471470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        pw.println();
472470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        mResponseQueue.dump(fd, pw, args);
473470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt    }
474470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt
475470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt    private void log(String logstring) {
476470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt        if (LOGD) Slog.d(TAG, logstring);
477470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt        mLocalLog.log(logstring);
478470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt    }
479470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt
480470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt    private void loge(String logstring) {
481470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        Slog.e(TAG, logstring);
482470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        mLocalLog.log(logstring);
483470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt    }
484470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt
485470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt    private static class ResponseQueue {
486470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt
487ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt        private static class PendingCmd {
48856cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            public final int cmdNum;
48956cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            public final String logCmd;
49056cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey
491ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            public BlockingQueue<NativeDaemonEvent> responses =
492ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                    new ArrayBlockingQueue<NativeDaemonEvent>(10);
493ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt
494ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // The availableResponseCount member is used to track when we can remove this
495ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // instance from the ResponseQueue.
496ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // This is used under the protection of a sync of the mPendingCmds object.
497ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // A positive value means we've had more writers retreive this object while
498ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // a negative value means we've had more readers.  When we've had an equal number
499ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // (it goes to zero) we can remove this object from the mPendingCmds list.
500ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // Note that we may have more responses for this command (and more readers
501ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // coming), but that would result in a new PendingCmd instance being created
502ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // and added with the same cmdNum.
503ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // Also note that when this goes to zero it just means a parity of readers and
504ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // writers have retrieved this object - not that they are done using it.  The
505ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // responses queue may well have more responses yet to be read or may get more
506ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // responses added to it.  But all those readers/writers have retreived and
507ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // hold references to this instance already so it can be removed from
508ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // mPendingCmds queue.
509ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            public int availableResponseCount;
51056cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey
51156cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            public PendingCmd(int cmdNum, String logCmd) {
51256cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey                this.cmdNum = cmdNum;
51356cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey                this.logCmd = logCmd;
51456cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            }
515470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        }
516470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt
517ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt        private final LinkedList<PendingCmd> mPendingCmds;
518470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        private int mMaxCount;
519470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt
520470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        ResponseQueue(int maxCount) {
521ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            mPendingCmds = new LinkedList<PendingCmd>();
522470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt            mMaxCount = maxCount;
523470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        }
524470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt
525470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        public void add(int cmdNum, NativeDaemonEvent response) {
526ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            PendingCmd found = null;
527ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            synchronized (mPendingCmds) {
528ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                for (PendingCmd pendingCmd : mPendingCmds) {
529ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                    if (pendingCmd.cmdNum == cmdNum) {
530ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                        found = pendingCmd;
531470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                        break;
532470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                    }
533470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                }
534470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                if (found == null) {
535470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                    // didn't find it - make sure our queue isn't too big before adding
536ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                    while (mPendingCmds.size() >= mMaxCount) {
537470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                        Slog.e("NativeDaemonConnector.ResponseQueue",
538ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                                "more buffered than allowed: " + mPendingCmds.size() +
539470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                                " >= " + mMaxCount);
540470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                        // let any waiter timeout waiting for this
541ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                        PendingCmd pendingCmd = mPendingCmds.remove();
542470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                        Slog.e("NativeDaemonConnector.ResponseQueue",
54356cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey                                "Removing request: " + pendingCmd.logCmd + " (" +
544ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                                pendingCmd.cmdNum + ")");
545470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                    }
546ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                    found = new PendingCmd(cmdNum, null);
547ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                    mPendingCmds.add(found);
548470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                }
549ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                found.availableResponseCount++;
550ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                // if a matching remove call has already retrieved this we can remove this
551ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                // instance from our list
552ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                if (found.availableResponseCount == 0) mPendingCmds.remove(found);
553470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt            }
554ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            try {
555ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                found.responses.put(response);
556ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            } catch (InterruptedException e) { }
557470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        }
558470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt
559470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        // note that the timeout does not count time in deep sleep.  If you don't want
560470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        // the device to sleep, hold a wakelock
56156cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        public NativeDaemonEvent remove(int cmdNum, int timeoutMs, String logCmd) {
562ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            PendingCmd found = null;
563ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            synchronized (mPendingCmds) {
564ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                for (PendingCmd pendingCmd : mPendingCmds) {
565ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                    if (pendingCmd.cmdNum == cmdNum) {
566ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                        found = pendingCmd;
567ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                        break;
568470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                    }
569470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                }
570ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                if (found == null) {
57156cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey                    found = new PendingCmd(cmdNum, logCmd);
572ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                    mPendingCmds.add(found);
573470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                }
574ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                found.availableResponseCount--;
575ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                // if a matching add call has already retrieved this we can remove this
576ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                // instance from our list
577ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                if (found.availableResponseCount == 0) mPendingCmds.remove(found);
578ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            }
579ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            NativeDaemonEvent result = null;
580ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            try {
581ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
582ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            } catch (InterruptedException e) {}
583ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            if (result == null) {
584ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                Slog.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
585470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt            }
586ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            return result;
587470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        }
588470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt
589470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
590470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt            pw.println("Pending requests:");
591ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            synchronized (mPendingCmds) {
592ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                for (PendingCmd pendingCmd : mPendingCmds) {
59356cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey                    pw.println("  Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.logCmd);
594470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                }
595470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt            }
596470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        }
597470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt    }
59867bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat}
599