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;
232ffa11e4b71c545e34533ef827bdc1a07fbe8246Dianne Hackbornimport android.os.Looper;
24e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yehimport android.os.Message;
2577b987f1a1bb6028a871de01065b94c4cfff0b5cDianne Hackbornimport android.os.PowerManager;
2667bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehatimport android.os.SystemClock;
27470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwaltimport android.util.LocalLog;
288a9b22056b13477f59df934928c00c58b5871c95Joe Onoratoimport android.util.Slog;
2967bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
308b2c3a14603d163d7564e6f60286995079687690Jeff Sharkeyimport com.android.internal.annotations.VisibleForTesting;
3131c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkeyimport com.google.android.collect.Lists;
3231c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
33470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwaltimport java.io.FileDescriptor;
3467bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehatimport java.io.IOException;
3567bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehatimport java.io.InputStream;
3667bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehatimport java.io.OutputStream;
37470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwaltimport java.io.PrintWriter;
38d396a448b2e36e29598c954b64bfddef73f3fae0Elliott Hughesimport java.nio.charset.StandardCharsets;
3967bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehatimport java.util.ArrayList;
40470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwaltimport java.util.concurrent.atomic.AtomicInteger;
41ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwaltimport java.util.concurrent.ArrayBlockingQueue;
42ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwaltimport java.util.concurrent.BlockingQueue;
43ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwaltimport java.util.concurrent.TimeUnit;
44470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwaltimport java.util.LinkedList;
4567bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
4667bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat/**
4731c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey * Generic connector class for interfacing with a native daemon which uses the
4831c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey * {@code libsysutils} FrameworkListener protocol.
4967bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat */
50fa23c5ae226c1a1d39f89c5c87d4f340e91d90e0Jeff Sharkeyfinal class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
5131c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    private static final boolean LOGD = false;
5267bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
537f44ff8d6b4d115420e7bd5f08f12825738afd54Robert Greenwalt    private final static boolean VDBG = false;
547f44ff8d6b4d115420e7bd5f08f12825738afd54Robert Greenwalt
5531c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    private final String TAG;
5667bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
5731c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    private String mSocket;
5831c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    private OutputStream mOutputStream;
59470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt    private LocalLog mLocalLog;
60961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root
61470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt    private final ResponseQueue mResponseQueue;
6267bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
6377b987f1a1bb6028a871de01065b94c4cfff0b5cDianne Hackborn    private final PowerManager.WakeLock mWakeLock;
6477b987f1a1bb6028a871de01065b94c4cfff0b5cDianne Hackborn
652ffa11e4b71c545e34533ef827bdc1a07fbe8246Dianne Hackborn    private final Looper mLooper;
662ffa11e4b71c545e34533ef827bdc1a07fbe8246Dianne Hackborn
6731c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    private INativeDaemonConnectorCallbacks mCallbacks;
6831c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    private Handler mCallbackHandler;
6967bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
70470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt    private AtomicInteger mSequenceNumber;
71470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt
72470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt    private static final int DEFAULT_TIMEOUT = 1 * 60 * 1000; /* 1 minute */
735a0c320a820a0ccaafaa87ad858a29bf5d88a8b9Robert Greenwalt    private static final long WARN_EXECUTE_DELAY_MS = 500; /* .5 sec */
74470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt
7531c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    /** Lock held whenever communicating with native daemon. */
7631c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    private final Object mDaemonLock = new Object();
7767bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
7831c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    private final int BUFFER_SIZE = 4096;
7967bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
8031c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
8177b987f1a1bb6028a871de01065b94c4cfff0b5cDianne Hackborn            int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl) {
822ffa11e4b71c545e34533ef827bdc1a07fbe8246Dianne Hackborn        this(callbacks, socket, responseQueueSize, logTag, maxLogSize, wl,
832ffa11e4b71c545e34533ef827bdc1a07fbe8246Dianne Hackborn                FgThread.get().getLooper());
842ffa11e4b71c545e34533ef827bdc1a07fbe8246Dianne Hackborn    }
852ffa11e4b71c545e34533ef827bdc1a07fbe8246Dianne Hackborn
862ffa11e4b71c545e34533ef827bdc1a07fbe8246Dianne Hackborn    NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
872ffa11e4b71c545e34533ef827bdc1a07fbe8246Dianne Hackborn            int responseQueueSize, String logTag, int maxLogSize, PowerManager.WakeLock wl,
882ffa11e4b71c545e34533ef827bdc1a07fbe8246Dianne Hackborn            Looper looper) {
8967bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat        mCallbacks = callbacks;
9067bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat        mSocket = socket;
91470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        mResponseQueue = new ResponseQueue(responseQueueSize);
9277b987f1a1bb6028a871de01065b94c4cfff0b5cDianne Hackborn        mWakeLock = wl;
9377b987f1a1bb6028a871de01065b94c4cfff0b5cDianne Hackborn        if (mWakeLock != null) {
9477b987f1a1bb6028a871de01065b94c4cfff0b5cDianne Hackborn            mWakeLock.setReferenceCounted(true);
9577b987f1a1bb6028a871de01065b94c4cfff0b5cDianne Hackborn        }
962ffa11e4b71c545e34533ef827bdc1a07fbe8246Dianne Hackborn        mLooper = looper;
97470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        mSequenceNumber = new AtomicInteger(0);
9831c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        TAG = logTag != null ? logTag : "NativeDaemonConnector";
99470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt        mLocalLog = new LocalLog(maxLogSize);
10067bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat    }
10167bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
102e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yeh    @Override
10367bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat    public void run() {
1042ffa11e4b71c545e34533ef827bdc1a07fbe8246Dianne Hackborn        mCallbackHandler = new Handler(mLooper, this);
10567bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
10667bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat        while (true) {
10767bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat            try {
10867bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat                listenToSocket();
10967bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat            } catch (Exception e) {
110470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                loge("Error in NativeDaemonConnector: " + e);
1114c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat                SystemClock.sleep(5000);
11267bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat            }
11367bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat        }
11467bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat    }
11567bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
116e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yeh    @Override
117e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yeh    public boolean handleMessage(Message msg) {
118e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yeh        String event = (String) msg.obj;
119e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yeh        try {
1202d34b4a88531e51982b030c43d672ec2cc3d8f36Robert Greenwalt            if (!mCallbacks.onEvent(msg.what, event, NativeDaemonEvent.unescapeArgs(event))) {
121470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                log(String.format("Unhandled event '%s'", event));
122e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yeh            }
123e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yeh        } catch (Exception e) {
124470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt            loge("Error handling '" + event + "': " + e);
12577b987f1a1bb6028a871de01065b94c4cfff0b5cDianne Hackborn        } finally {
1264590e52f3d0558e01322fe4dd55bb612afdfb079Dianne Hackborn            if (mCallbacks.onCheckHoldWakeLock(msg.what) && mWakeLock != null) {
12777b987f1a1bb6028a871de01065b94c4cfff0b5cDianne Hackborn                mWakeLock.release();
12877b987f1a1bb6028a871de01065b94c4cfff0b5cDianne Hackborn            }
129e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yeh        }
130e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yeh        return true;
131e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yeh    }
132e5750a344a9c1a83a63d5119d39d2ea4897bc312Chia-chi Yeh
1337421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti    private LocalSocketAddress determineSocketAddress() {
1347421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti        // If we're testing, set up a socket in a namespace that's accessible to test code.
1357421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti        // In order to ensure that unprivileged apps aren't able to impersonate native daemons on
1367421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti        // production devices, even if said native daemons ill-advisedly pick a socket name that
1377421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti        // starts with __test__, only allow this on debug builds.
1387421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti        if (mSocket.startsWith("__test__") && Build.IS_DEBUGGABLE) {
1397421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti            return new LocalSocketAddress(mSocket);
1407421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti        } else {
1417421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti            return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED);
1427421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti        }
1437421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti    }
1447421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti
1454c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat    private void listenToSocket() throws IOException {
146961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root        LocalSocket socket = null;
14767bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
14867bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat        try {
14967bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat            socket = new LocalSocket();
1507421a01f18f34d554ca7a9fd987c4f96da2bdf2fLorenzo Colitti            LocalSocketAddress address = determineSocketAddress();
15167bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
15267bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat            socket.connect(address);
15367bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
15467bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat            InputStream inputStream = socket.getInputStream();
155470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt            synchronized (mDaemonLock) {
156470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                mOutputStream = socket.getOutputStream();
157470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt            }
15867bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
159030bc88b4c09a41b0d7dbe249aec55f33d6b8b8aanga            mCallbacks.onDaemonConnected();
160030bc88b4c09a41b0d7dbe249aec55f33d6b8b8aanga
161961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root            byte[] buffer = new byte[BUFFER_SIZE];
162961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root            int start = 0;
16367bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
16467bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat            while (true) {
165961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root                int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
166470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                if (count < 0) {
167470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                    loge("got " + count + " reading with start = " + start);
168470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                    break;
169470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                }
17067bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
17112da9d7472ae87b841575d5358e19f143d12f900Kenny Root                // Add our starting point to the count and reset the start.
17212da9d7472ae87b841575d5358e19f143d12f900Kenny Root                count += start;
17312da9d7472ae87b841575d5358e19f143d12f900Kenny Root                start = 0;
17412da9d7472ae87b841575d5358e19f143d12f900Kenny Root
17567bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat                for (int i = 0; i < count; i++) {
17667bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat                    if (buffer[i] == 0) {
177c38182ff3b1ecaf5e7a7270074bbab7f37819d3dPaul Lawrence                        // Note - do not log this raw message since it may contain
178c38182ff3b1ecaf5e7a7270074bbab7f37819d3dPaul Lawrence                        // sensitive data
179ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey                        final String rawEvent = new String(
180d396a448b2e36e29598c954b64bfddef73f3fae0Elliott Hughes                                buffer, start, i - start, StandardCharsets.UTF_8);
18167bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
18277b987f1a1bb6028a871de01065b94c4cfff0b5cDianne Hackborn                        boolean releaseWl = false;
18367bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat                        try {
18431c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey                            final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(
18531c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey                                    rawEvent);
186c38182ff3b1ecaf5e7a7270074bbab7f37819d3dPaul Lawrence
187c38182ff3b1ecaf5e7a7270074bbab7f37819d3dPaul Lawrence                            log("RCV <- {" + event + "}");
188c38182ff3b1ecaf5e7a7270074bbab7f37819d3dPaul Lawrence
18931c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey                            if (event.isClassUnsolicited()) {
190ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey                                // TODO: migrate to sending NativeDaemonEvent instances
1914590e52f3d0558e01322fe4dd55bb612afdfb079Dianne Hackborn                                if (mCallbacks.onCheckHoldWakeLock(event.getCode())
1924590e52f3d0558e01322fe4dd55bb612afdfb079Dianne Hackborn                                        && mWakeLock != null) {
19377b987f1a1bb6028a871de01065b94c4cfff0b5cDianne Hackborn                                    mWakeLock.acquire();
19477b987f1a1bb6028a871de01065b94c4cfff0b5cDianne Hackborn                                    releaseWl = true;
19577b987f1a1bb6028a871de01065b94c4cfff0b5cDianne Hackborn                                }
19677b987f1a1bb6028a871de01065b94c4cfff0b5cDianne Hackborn                                if (mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
19777b987f1a1bb6028a871de01065b94c4cfff0b5cDianne Hackborn                                        event.getCode(), event.getRawEvent()))) {
19877b987f1a1bb6028a871de01065b94c4cfff0b5cDianne Hackborn                                    releaseWl = false;
19977b987f1a1bb6028a871de01065b94c4cfff0b5cDianne Hackborn                                }
2001cd94ef9570a1534e32b27d5b174dc690c9be6b9Irfan Sheriff                            } else {
201470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                                mResponseQueue.add(event.getCmdNumber(), event);
20267bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat                            }
20331c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey                        } catch (IllegalArgumentException e) {
204c38182ff3b1ecaf5e7a7270074bbab7f37819d3dPaul Lawrence                            log("Problem parsing message " + e);
20577b987f1a1bb6028a871de01065b94c4cfff0b5cDianne Hackborn                        } finally {
20677b987f1a1bb6028a871de01065b94c4cfff0b5cDianne Hackborn                            if (releaseWl) {
20777b987f1a1bb6028a871de01065b94c4cfff0b5cDianne Hackborn                                mWakeLock.acquire();
20877b987f1a1bb6028a871de01065b94c4cfff0b5cDianne Hackborn                            }
20967bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat                        }
21031c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
21167bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat                        start = i + 1;
21267bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat                    }
21367bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat                }
214c38182ff3b1ecaf5e7a7270074bbab7f37819d3dPaul Lawrence
215f0be1d89bf1cf5592ea1786d837f4f2329bdf66dRobert Greenwalt                if (start == 0) {
216c38182ff3b1ecaf5e7a7270074bbab7f37819d3dPaul Lawrence                    log("RCV incomplete");
217f0be1d89bf1cf5592ea1786d837f4f2329bdf66dRobert Greenwalt                }
21812da9d7472ae87b841575d5358e19f143d12f900Kenny Root
21912da9d7472ae87b841575d5358e19f143d12f900Kenny Root                // We should end at the amount we read. If not, compact then
22012da9d7472ae87b841575d5358e19f143d12f900Kenny Root                // buffer and read again.
221961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root                if (start != count) {
222961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root                    final int remaining = BUFFER_SIZE - start;
223961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root                    System.arraycopy(buffer, start, buffer, 0, remaining);
224961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root                    start = remaining;
225961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root                } else {
226961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root                    start = 0;
227961aa8c8879e9f68c0eddcaf87565200a4347134Kenny Root                }
22867bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat            }
22967bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat        } catch (IOException ex) {
230470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt            loge("Communications error: " + ex);
2314c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat            throw ex;
2324c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat        } finally {
233fa23c5ae226c1a1d39f89c5c87d4f340e91d90e0Jeff Sharkey            synchronized (mDaemonLock) {
2344c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat                if (mOutputStream != null) {
2354c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat                    try {
236470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                        loge("closing stream for " + mSocket);
2374c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat                        mOutputStream.close();
2384c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat                    } catch (IOException e) {
239470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                        loge("Failed closing output stream: " + e);
2404c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat                    }
2414c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat                    mOutputStream = null;
24267bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat                }
24367bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat            }
24467bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
2454c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat            try {
2464c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat                if (socket != null) {
2474c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat                    socket.close();
2484c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat                }
2494c27e0e3bed006a9ba45c6c02be5fe49827b3feaSan Mehat            } catch (IOException ex) {
250470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                loge("Failed closing socket: " + ex);
25167bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat            }
25267bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat        }
25367bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat    }
25467bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
25567bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat    /**
25656cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey     * Wrapper around argument that indicates it's sensitive and shouldn't be
25756cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey     * logged.
25856cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey     */
25956cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey    public static class SensitiveArg {
26056cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        private final Object mArg;
26156cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey
26256cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        public SensitiveArg(Object arg) {
26356cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            mArg = arg;
26456cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        }
26556cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey
26656cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        @Override
26756cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        public String toString() {
26856cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            return String.valueOf(mArg);
26956cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        }
27056cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey    }
27156cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey
27256cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey    /**
273470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt     * Make command for daemon, escaping arguments as needed.
27467bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat     */
27556cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey    @VisibleForTesting
27656cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey    static void makeCommand(StringBuilder rawBuilder, StringBuilder logBuilder, int sequenceNumber,
27756cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            String cmd, Object... args) {
27831c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        if (cmd.indexOf('\0') >= 0) {
2797b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey            throw new IllegalArgumentException("Unexpected command: " + cmd);
2807b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey        }
2817b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey        if (cmd.indexOf(' ') >= 0) {
2827b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey            throw new IllegalArgumentException("Arguments must be separate from command");
283b0aec07f7462ff7563835c3107f4b46a28eae7a4Jeff Sharkey        }
28431c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
28556cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        rawBuilder.append(sequenceNumber).append(' ').append(cmd);
28656cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        logBuilder.append(sequenceNumber).append(' ').append(cmd);
28731c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        for (Object arg : args) {
28831c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            final String argString = String.valueOf(arg);
28931c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            if (argString.indexOf('\0') >= 0) {
2907b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey                throw new IllegalArgumentException("Unexpected argument: " + arg);
29131c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            }
29231c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
29356cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            rawBuilder.append(' ');
29456cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            logBuilder.append(' ');
29556cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey
29656cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            appendEscaped(rawBuilder, argString);
29756cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            if (arg instanceof SensitiveArg) {
29856cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey                logBuilder.append("[scrubbed]");
29956cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            } else {
30056cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey                appendEscaped(logBuilder, argString);
30156cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            }
302b0aec07f7462ff7563835c3107f4b46a28eae7a4Jeff Sharkey        }
30356cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey
30456cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        rawBuilder.append('\0');
30567bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat    }
30667bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
307deba6935c6595c724416cde3368a92c32d8f8683San Mehat    /**
308ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * Issue the given command to the native daemon and return a single expected
309ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * response.
310ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *
311ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * @throws NativeDaemonConnectorException when problem communicating with
312ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             native daemon, or if the response matches
313ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             {@link NativeDaemonEvent#isClassClientError()} or
314ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             {@link NativeDaemonEvent#isClassServerError()}.
315deba6935c6595c724416cde3368a92c32d8f8683San Mehat     */
316ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
317ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        return execute(cmd.mCmd, cmd.mArguments.toArray());
318ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    }
319ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey
320ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    /**
321ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * Issue the given command to the native daemon and return a single expected
3227b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey     * response. Any arguments must be separated from base command so they can
3237b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey     * be properly escaped.
324ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *
325ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * @throws NativeDaemonConnectorException when problem communicating with
326ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             native daemon, or if the response matches
327ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             {@link NativeDaemonEvent#isClassClientError()} or
328ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             {@link NativeDaemonEvent#isClassServerError()}.
329ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     */
330ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    public NativeDaemonEvent execute(String cmd, Object... args)
331ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey            throws NativeDaemonConnectorException {
332ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        final NativeDaemonEvent[] events = executeForList(cmd, args);
333ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        if (events.length != 1) {
334ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey            throw new NativeDaemonConnectorException(
335ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey                    "Expected exactly one response, but received " + events.length);
336ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        }
337ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        return events[0];
338ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    }
339ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey
340ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    /**
341ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * Issue the given command to the native daemon and return any
342ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * {@link NativeDaemonEvent#isClassContinue()} responses, including the
343ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * final terminal response.
344ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *
345ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * @throws NativeDaemonConnectorException when problem communicating with
346ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             native daemon, or if the response matches
347ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             {@link NativeDaemonEvent#isClassClientError()} or
348ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             {@link NativeDaemonEvent#isClassServerError()}.
349ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     */
350ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException {
351ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        return executeForList(cmd.mCmd, cmd.mArguments.toArray());
352ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    }
353ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey
354ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    /**
355ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * Issue the given command to the native daemon and return any
356ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * {@link NativeDaemonEvent#isClassContinue()} responses, including the
3577b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey     * final terminal response. Any arguments must be separated from base
3587b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey     * command so they can be properly escaped.
359ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *
360ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     * @throws NativeDaemonConnectorException when problem communicating with
361ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             native daemon, or if the response matches
362ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             {@link NativeDaemonEvent#isClassClientError()} or
363ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     *             {@link NativeDaemonEvent#isClassServerError()}.
364ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     */
365ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    public NativeDaemonEvent[] executeForList(String cmd, Object... args)
36631c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            throws NativeDaemonConnectorException {
367470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt            return execute(DEFAULT_TIMEOUT, cmd, args);
368fa23c5ae226c1a1d39f89c5c87d4f340e91d90e0Jeff Sharkey    }
369fa23c5ae226c1a1d39f89c5c87d4f340e91d90e0Jeff Sharkey
370470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt    /**
3717b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey     * Issue the given command to the native daemon and return any {@linke
3727b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey     * NativeDaemonEvent@isClassContinue()} responses, including the final
3737b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey     * terminal response. Note that the timeout does not count time in deep
3747b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey     * sleep. Any arguments must be separated from base command so they can be
3757b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey     * properly escaped.
376470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt     *
377470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt     * @throws NativeDaemonConnectorException when problem communicating with
378470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt     *             native daemon, or if the response matches
379470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt     *             {@link NativeDaemonEvent#isClassClientError()} or
380470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt     *             {@link NativeDaemonEvent#isClassServerError()}.
381470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt     */
382470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt    public NativeDaemonEvent[] execute(int timeout, String cmd, Object... args)
38331c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            throws NativeDaemonConnectorException {
38456cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        final long startTime = SystemClock.elapsedRealtime();
38556cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey
38631c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
387d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt
38856cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        final StringBuilder rawBuilder = new StringBuilder();
38956cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        final StringBuilder logBuilder = new StringBuilder();
390d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt        final int sequenceNumber = mSequenceNumber.incrementAndGet();
391d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt
39256cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        makeCommand(rawBuilder, logBuilder, sequenceNumber, cmd, args);
393d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt
39456cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        final String rawCmd = rawBuilder.toString();
39556cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        final String logCmd = logBuilder.toString();
396d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt
39756cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        log("SND -> {" + logCmd + "}");
398d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt
399d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt        synchronized (mDaemonLock) {
400d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt            if (mOutputStream == null) {
401d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt                throw new NativeDaemonConnectorException("missing output stream");
402d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt            } else {
403d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt                try {
404b8292830f79fc76ffb9a1be5cd316212ac494d03Elliott Hughes                    mOutputStream.write(rawCmd.getBytes(StandardCharsets.UTF_8));
405d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt                } catch (IOException e) {
406d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt                    throw new NativeDaemonConnectorException("problem sending command", e);
407d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt                }
408d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt            }
409d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt        }
41067bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
41131c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        NativeDaemonEvent event = null;
41231c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        do {
41356cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            event = mResponseQueue.remove(sequenceNumber, timeout, logCmd);
414470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt            if (event == null) {
415d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt                loge("timed-out waiting for response to " + logCmd);
416d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt                throw new NativeDaemonFailureException(logCmd, event);
41767bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat            }
4187f44ff8d6b4d115420e7bd5f08f12825738afd54Robert Greenwalt            if (VDBG) log("RMV <- {" + event + "}");
41931c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            events.add(event);
42031c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        } while (event.isClassContinue());
42131c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
4225a0c320a820a0ccaafaa87ad858a29bf5d88a8b9Robert Greenwalt        final long endTime = SystemClock.elapsedRealtime();
4235a0c320a820a0ccaafaa87ad858a29bf5d88a8b9Robert Greenwalt        if (endTime - startTime > WARN_EXECUTE_DELAY_MS) {
4245a0c320a820a0ccaafaa87ad858a29bf5d88a8b9Robert Greenwalt            loge("NDC Command {" + logCmd + "} took too long (" + (endTime - startTime) + "ms)");
4255a0c320a820a0ccaafaa87ad858a29bf5d88a8b9Robert Greenwalt        }
4265a0c320a820a0ccaafaa87ad858a29bf5d88a8b9Robert Greenwalt
42731c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        if (event.isClassClientError()) {
428d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt            throw new NativeDaemonArgumentException(logCmd, event);
42931c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        }
43031c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        if (event.isClassServerError()) {
431d192598d3e7c6f38fc9deb573b06ababa56d741aRobert Greenwalt            throw new NativeDaemonFailureException(logCmd, event);
43267bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat        }
43367bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat
43431c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        return events.toArray(new NativeDaemonEvent[events.size()]);
43531c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    }
43631c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
43731c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    /**
43831c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey     * Append the given argument to {@link StringBuilder}, escaping as needed,
43931c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey     * and surrounding with quotes when it contains spaces.
44031c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey     */
4418b2c3a14603d163d7564e6f60286995079687690Jeff Sharkey    @VisibleForTesting
44231c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    static void appendEscaped(StringBuilder builder, String arg) {
44331c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        final boolean hasSpaces = arg.indexOf(' ') >= 0;
44431c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        if (hasSpaces) {
44531c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            builder.append('"');
44631c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        }
44731c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
44831c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        final int length = arg.length();
44931c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        for (int i = 0; i < length; i++) {
45031c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            final char c = arg.charAt(i);
45131c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
45231c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            if (c == '"') {
45331c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey                builder.append("\\\"");
45431c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            } else if (c == '\\') {
45531c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey                builder.append("\\\\");
45631c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            } else {
45731c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey                builder.append(c);
458deba6935c6595c724416cde3368a92c32d8f8683San Mehat            }
459deba6935c6595c724416cde3368a92c32d8f8683San Mehat        }
46031c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
46131c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        if (hasSpaces) {
46231c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            builder.append('"');
46331c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        }
46431c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    }
46531c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
46631c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
46731c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
46831c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            super(command, event);
46931c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        }
47031c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
47131c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        @Override
47231c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        public IllegalArgumentException rethrowAsParcelableException() {
47331c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            throw new IllegalArgumentException(getMessage(), this);
47431c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        }
47531c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    }
47631c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey
47731c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey    private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
47831c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
47931c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey            super(command, event);
48031c6e4817f6c967fc4f61c4f1d9f25743958f7deJeff Sharkey        }
481deba6935c6595c724416cde3368a92c32d8f8683San Mehat    }
482fa23c5ae226c1a1d39f89c5c87d4f340e91d90e0Jeff Sharkey
483ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    /**
4847b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey     * Command builder that handles argument list building. Any arguments must
4857b4596fd68a24643145e33f3dc4da9285d0f53aaJeff Sharkey     * be separated from base command so they can be properly escaped.
486ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey     */
487ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    public static class Command {
488ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        private String mCmd;
489ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        private ArrayList<Object> mArguments = Lists.newArrayList();
490ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey
491ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        public Command(String cmd, Object... args) {
492ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey            mCmd = cmd;
493ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey            for (Object arg : args) {
494ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey                appendArg(arg);
495ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey            }
496ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        }
497ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey
498ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        public Command appendArg(Object arg) {
499ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey            mArguments.add(arg);
500ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey            return this;
501ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey        }
502ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey    }
503ba2896e939f359e5857149f1a27212db71be012bJeff Sharkey
504fa23c5ae226c1a1d39f89c5c87d4f340e91d90e0Jeff Sharkey    /** {@inheritDoc} */
505fa23c5ae226c1a1d39f89c5c87d4f340e91d90e0Jeff Sharkey    public void monitor() {
506fa23c5ae226c1a1d39f89c5c87d4f340e91d90e0Jeff Sharkey        synchronized (mDaemonLock) { }
507fa23c5ae226c1a1d39f89c5c87d4f340e91d90e0Jeff Sharkey    }
508470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt
509470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
510470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt        mLocalLog.dump(fd, pw, args);
511470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        pw.println();
512470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        mResponseQueue.dump(fd, pw, args);
513470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt    }
514470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt
515470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt    private void log(String logstring) {
516470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt        if (LOGD) Slog.d(TAG, logstring);
517470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt        mLocalLog.log(logstring);
518470fd72a06390d7a6b854583afd0ed76ce0a03eeRobert Greenwalt    }
519470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt
520470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt    private void loge(String logstring) {
521470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        Slog.e(TAG, logstring);
522470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        mLocalLog.log(logstring);
523470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt    }
524470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt
525470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt    private static class ResponseQueue {
526470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt
527ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt        private static class PendingCmd {
52856cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            public final int cmdNum;
52956cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            public final String logCmd;
53056cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey
531ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            public BlockingQueue<NativeDaemonEvent> responses =
532ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                    new ArrayBlockingQueue<NativeDaemonEvent>(10);
533ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt
534ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // The availableResponseCount member is used to track when we can remove this
535ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // instance from the ResponseQueue.
536ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // This is used under the protection of a sync of the mPendingCmds object.
537ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // A positive value means we've had more writers retreive this object while
538ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // a negative value means we've had more readers.  When we've had an equal number
539ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // (it goes to zero) we can remove this object from the mPendingCmds list.
540ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // Note that we may have more responses for this command (and more readers
541ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // coming), but that would result in a new PendingCmd instance being created
542ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // and added with the same cmdNum.
543ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // Also note that when this goes to zero it just means a parity of readers and
544ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // writers have retrieved this object - not that they are done using it.  The
545ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // responses queue may well have more responses yet to be read or may get more
546ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // responses added to it.  But all those readers/writers have retreived and
547ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // hold references to this instance already so it can be removed from
548ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            // mPendingCmds queue.
549ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            public int availableResponseCount;
55056cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey
55156cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            public PendingCmd(int cmdNum, String logCmd) {
55256cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey                this.cmdNum = cmdNum;
55356cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey                this.logCmd = logCmd;
55456cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey            }
555470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        }
556470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt
557ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt        private final LinkedList<PendingCmd> mPendingCmds;
558470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        private int mMaxCount;
559470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt
560470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        ResponseQueue(int maxCount) {
561ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            mPendingCmds = new LinkedList<PendingCmd>();
562470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt            mMaxCount = maxCount;
563470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        }
564470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt
565470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        public void add(int cmdNum, NativeDaemonEvent response) {
566ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            PendingCmd found = null;
567ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            synchronized (mPendingCmds) {
568ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                for (PendingCmd pendingCmd : mPendingCmds) {
569ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                    if (pendingCmd.cmdNum == cmdNum) {
570ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                        found = pendingCmd;
571470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                        break;
572470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                    }
573470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                }
574470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                if (found == null) {
575470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                    // didn't find it - make sure our queue isn't too big before adding
576ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                    while (mPendingCmds.size() >= mMaxCount) {
577470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                        Slog.e("NativeDaemonConnector.ResponseQueue",
578ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                                "more buffered than allowed: " + mPendingCmds.size() +
579470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                                " >= " + mMaxCount);
580470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                        // let any waiter timeout waiting for this
581ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                        PendingCmd pendingCmd = mPendingCmds.remove();
582470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                        Slog.e("NativeDaemonConnector.ResponseQueue",
58356cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey                                "Removing request: " + pendingCmd.logCmd + " (" +
584ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                                pendingCmd.cmdNum + ")");
585470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                    }
586ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                    found = new PendingCmd(cmdNum, null);
587ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                    mPendingCmds.add(found);
588470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                }
589ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                found.availableResponseCount++;
590ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                // if a matching remove call has already retrieved this we can remove this
591ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                // instance from our list
592ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                if (found.availableResponseCount == 0) mPendingCmds.remove(found);
593470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt            }
594ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            try {
595ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                found.responses.put(response);
596ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            } catch (InterruptedException e) { }
597470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        }
598470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt
599470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        // note that the timeout does not count time in deep sleep.  If you don't want
600470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        // the device to sleep, hold a wakelock
60156cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey        public NativeDaemonEvent remove(int cmdNum, int timeoutMs, String logCmd) {
602ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            PendingCmd found = null;
603ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            synchronized (mPendingCmds) {
604ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                for (PendingCmd pendingCmd : mPendingCmds) {
605ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                    if (pendingCmd.cmdNum == cmdNum) {
606ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                        found = pendingCmd;
607ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                        break;
608470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                    }
609470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                }
610ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                if (found == null) {
61156cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey                    found = new PendingCmd(cmdNum, logCmd);
612ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                    mPendingCmds.add(found);
613470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                }
614ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                found.availableResponseCount--;
615ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                // if a matching add call has already retrieved this we can remove this
616ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                // instance from our list
617ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                if (found.availableResponseCount == 0) mPendingCmds.remove(found);
618ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            }
619ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            NativeDaemonEvent result = null;
620ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            try {
621ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                result = found.responses.poll(timeoutMs, TimeUnit.MILLISECONDS);
622ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            } catch (InterruptedException e) {}
623ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            if (result == null) {
624ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                Slog.e("NativeDaemonConnector.ResponseQueue", "Timeout waiting for response");
625470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt            }
626ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            return result;
627470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        }
628470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt
629470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
630470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt            pw.println("Pending requests:");
631ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt            synchronized (mPendingCmds) {
632ef21599b17bc081321bc49f1fb315c9e7ad31e3aRobert Greenwalt                for (PendingCmd pendingCmd : mPendingCmds) {
63356cd646abeae51e806791f82ab0995fe047b1fe4Jeff Sharkey                    pw.println("  Cmd " + pendingCmd.cmdNum + " - " + pendingCmd.logCmd);
634470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt                }
635470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt            }
636470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt        }
637470007f69a258ccebb7c04927210a091dbcbe181Robert Greenwalt    }
63867bd2cd75f0615c1a08a221f2114d2acda90d1ceSan Mehat}
639