blob: 308661f1b0672c1788fdba525468b2597f192fda [file] [log] [blame]
San Mehat67bd2cd2010-01-12 12:18:49 -08001/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server;
18
San Mehat67bd2cd2010-01-12 12:18:49 -080019import android.net.LocalSocket;
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070020import android.net.LocalSocketAddress;
Chia-chi Yehe5750a32011-08-03 14:42:11 -070021import android.os.Handler;
22import android.os.HandlerThread;
23import android.os.Message;
San Mehat67bd2cd2010-01-12 12:18:49 -080024import android.os.SystemClock;
Robert Greenwalt470fd722012-01-18 12:51:15 -080025import android.util.LocalLog;
Joe Onorato8a9b2202010-02-26 18:56:32 -080026import android.util.Slog;
San Mehat67bd2cd2010-01-12 12:18:49 -080027
Jeff Sharkey31c6e482011-11-18 17:09:01 -080028import com.google.android.collect.Lists;
29
Robert Greenwalt470fd722012-01-18 12:51:15 -080030import java.nio.charset.Charsets;
31import java.io.FileDescriptor;
San Mehat67bd2cd2010-01-12 12:18:49 -080032import java.io.IOException;
33import java.io.InputStream;
34import java.io.OutputStream;
Robert Greenwalt470fd722012-01-18 12:51:15 -080035import java.io.PrintWriter;
San Mehat67bd2cd2010-01-12 12:18:49 -080036import java.util.ArrayList;
Guang Zhu6ffa7602012-02-07 19:14:02 -080037import java.util.concurrent.BlockingQueue;
38import java.util.concurrent.LinkedBlockingQueue;
San Mehat67bd2cd2010-01-12 12:18:49 -080039
40/**
Jeff Sharkey31c6e482011-11-18 17:09:01 -080041 * Generic connector class for interfacing with a native daemon which uses the
42 * {@code libsysutils} FrameworkListener protocol.
San Mehat67bd2cd2010-01-12 12:18:49 -080043 */
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070044final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
Jeff Sharkey31c6e482011-11-18 17:09:01 -080045 private static final boolean LOGD = false;
San Mehat67bd2cd2010-01-12 12:18:49 -080046
Jeff Sharkey31c6e482011-11-18 17:09:01 -080047 private final String TAG;
48
49 private String mSocket;
50 private OutputStream mOutputStream;
Robert Greenwalt470fd722012-01-18 12:51:15 -080051 private LocalLog mLocalLog;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080052
Guang Zhu6ffa7602012-02-07 19:14:02 -080053 private final BlockingQueue<NativeDaemonEvent> mResponseQueue;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080054
San Mehat67bd2cd2010-01-12 12:18:49 -080055 private INativeDaemonConnectorCallbacks mCallbacks;
Jeff Sharkey31c6e482011-11-18 17:09:01 -080056 private Handler mCallbackHandler;
San Mehat67bd2cd2010-01-12 12:18:49 -080057
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070058 /** Lock held whenever communicating with native daemon. */
Jeff Sharkey31c6e482011-11-18 17:09:01 -080059 private final Object mDaemonLock = new Object();
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -070060
Kenny Root961aa8c2010-03-22 18:02:45 -070061 private final int BUFFER_SIZE = 4096;
62
Jeff Sharkey31c6e482011-11-18 17:09:01 -080063 NativeDaemonConnector(INativeDaemonConnectorCallbacks callbacks, String socket,
Robert Greenwalt470fd722012-01-18 12:51:15 -080064 int responseQueueSize, String logTag, int maxLogSize) {
San Mehat67bd2cd2010-01-12 12:18:49 -080065 mCallbacks = callbacks;
San Mehat67bd2cd2010-01-12 12:18:49 -080066 mSocket = socket;
Guang Zhu6ffa7602012-02-07 19:14:02 -080067 mResponseQueue = new LinkedBlockingQueue<NativeDaemonEvent>(responseQueueSize);
Jeff Sharkey31c6e482011-11-18 17:09:01 -080068 TAG = logTag != null ? logTag : "NativeDaemonConnector";
Robert Greenwalt470fd722012-01-18 12:51:15 -080069 mLocalLog = new LocalLog(maxLogSize);
San Mehat67bd2cd2010-01-12 12:18:49 -080070 }
71
Chia-chi Yehe5750a32011-08-03 14:42:11 -070072 @Override
San Mehat67bd2cd2010-01-12 12:18:49 -080073 public void run() {
Chia-chi Yehe5750a32011-08-03 14:42:11 -070074 HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler");
75 thread.start();
76 mCallbackHandler = new Handler(thread.getLooper(), this);
San Mehat67bd2cd2010-01-12 12:18:49 -080077
78 while (true) {
79 try {
80 listenToSocket();
81 } catch (Exception e) {
Guang Zhu6ffa7602012-02-07 19:14:02 -080082 Slog.e(TAG, "Error in NativeDaemonConnector", e);
San Mehat4c27e0e2010-01-29 05:22:17 -080083 SystemClock.sleep(5000);
San Mehat67bd2cd2010-01-12 12:18:49 -080084 }
85 }
86 }
87
Chia-chi Yehe5750a32011-08-03 14:42:11 -070088 @Override
89 public boolean handleMessage(Message msg) {
90 String event = (String) msg.obj;
91 try {
92 if (!mCallbacks.onEvent(msg.what, event, event.split(" "))) {
Guang Zhu6ffa7602012-02-07 19:14:02 -080093 Slog.w(TAG, String.format(
94 "Unhandled event '%s'", event));
Chia-chi Yehe5750a32011-08-03 14:42:11 -070095 }
96 } catch (Exception e) {
Guang Zhu6ffa7602012-02-07 19:14:02 -080097 Slog.e(TAG, String.format(
98 "Error handling '%s'", event), e);
Chia-chi Yehe5750a32011-08-03 14:42:11 -070099 }
100 return true;
101 }
102
San Mehat4c27e0e2010-01-29 05:22:17 -0800103 private void listenToSocket() throws IOException {
Kenny Root961aa8c2010-03-22 18:02:45 -0700104 LocalSocket socket = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800105
106 try {
107 socket = new LocalSocket();
108 LocalSocketAddress address = new LocalSocketAddress(mSocket,
109 LocalSocketAddress.Namespace.RESERVED);
110
111 socket.connect(address);
San Mehat67bd2cd2010-01-12 12:18:49 -0800112
113 InputStream inputStream = socket.getInputStream();
Guang Zhu6ffa7602012-02-07 19:14:02 -0800114 mOutputStream = socket.getOutputStream();
San Mehat67bd2cd2010-01-12 12:18:49 -0800115
anga030bc882011-02-01 14:10:25 +0100116 mCallbacks.onDaemonConnected();
117
Kenny Root961aa8c2010-03-22 18:02:45 -0700118 byte[] buffer = new byte[BUFFER_SIZE];
119 int start = 0;
San Mehat67bd2cd2010-01-12 12:18:49 -0800120
121 while (true) {
Kenny Root961aa8c2010-03-22 18:02:45 -0700122 int count = inputStream.read(buffer, start, BUFFER_SIZE - start);
Guang Zhu6ffa7602012-02-07 19:14:02 -0800123 if (count < 0) break;
San Mehat67bd2cd2010-01-12 12:18:49 -0800124
Kenny Root12da9d72010-09-02 22:18:14 -0700125 // Add our starting point to the count and reset the start.
126 count += start;
127 start = 0;
128
San Mehat67bd2cd2010-01-12 12:18:49 -0800129 for (int i = 0; i < count; i++) {
130 if (buffer[i] == 0) {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800131 final String rawEvent = new String(
132 buffer, start, i - start, Charsets.UTF_8);
Robert Greenwalt470fd722012-01-18 12:51:15 -0800133 log("RCV <- {" + rawEvent + "}");
San Mehat67bd2cd2010-01-12 12:18:49 -0800134
San Mehat67bd2cd2010-01-12 12:18:49 -0800135 try {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800136 final NativeDaemonEvent event = NativeDaemonEvent.parseRawEvent(
137 rawEvent);
138 if (event.isClassUnsolicited()) {
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800139 // TODO: migrate to sending NativeDaemonEvent instances
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800140 mCallbackHandler.sendMessage(mCallbackHandler.obtainMessage(
141 event.getCode(), event.getRawEvent()));
Irfan Sheriff1cd94ef2011-01-16 14:31:55 -0800142 } else {
Guang Zhu6ffa7602012-02-07 19:14:02 -0800143 try {
144 mResponseQueue.put(event);
145 } catch (InterruptedException ex) {
146 Slog.e(TAG, "Failed to put response onto queue: " + ex);
147 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800148 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800149 } catch (IllegalArgumentException e) {
Guang Zhu6ffa7602012-02-07 19:14:02 -0800150 Slog.w(TAG, "Problem parsing message: " + rawEvent, e);
San Mehat67bd2cd2010-01-12 12:18:49 -0800151 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800152
San Mehat67bd2cd2010-01-12 12:18:49 -0800153 start = i + 1;
154 }
155 }
Robert Greenwaltf0be1d892012-01-20 16:33:15 -0800156 if (start == 0) {
157 final String rawEvent = new String(buffer, start, count, Charsets.UTF_8);
158 log("RCV incomplete <- {" + rawEvent + "}");
159 }
Kenny Root12da9d72010-09-02 22:18:14 -0700160
161 // We should end at the amount we read. If not, compact then
162 // buffer and read again.
Kenny Root961aa8c2010-03-22 18:02:45 -0700163 if (start != count) {
164 final int remaining = BUFFER_SIZE - start;
165 System.arraycopy(buffer, start, buffer, 0, remaining);
166 start = remaining;
167 } else {
168 start = 0;
169 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800170 }
171 } catch (IOException ex) {
Guang Zhu6ffa7602012-02-07 19:14:02 -0800172 Slog.e(TAG, "Communications error", ex);
San Mehat4c27e0e2010-01-29 05:22:17 -0800173 throw ex;
174 } finally {
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700175 synchronized (mDaemonLock) {
San Mehat4c27e0e2010-01-29 05:22:17 -0800176 if (mOutputStream != null) {
177 try {
178 mOutputStream.close();
179 } catch (IOException e) {
Guang Zhu6ffa7602012-02-07 19:14:02 -0800180 Slog.w(TAG, "Failed closing output stream", e);
San Mehat4c27e0e2010-01-29 05:22:17 -0800181 }
182 mOutputStream = null;
San Mehat67bd2cd2010-01-12 12:18:49 -0800183 }
San Mehat4c27e0e2010-01-29 05:22:17 -0800184 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800185
San Mehat4c27e0e2010-01-29 05:22:17 -0800186 try {
187 if (socket != null) {
188 socket.close();
189 }
190 } catch (IOException ex) {
Guang Zhu6ffa7602012-02-07 19:14:02 -0800191 Slog.w(TAG, "Failed closing socket", ex);
San Mehat67bd2cd2010-01-12 12:18:49 -0800192 }
193 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800194 }
195
San Mehat67bd2cd2010-01-12 12:18:49 -0800196 /**
Guang Zhu6ffa7602012-02-07 19:14:02 -0800197 * Send command to daemon, escaping arguments as needed.
San Mehat67bd2cd2010-01-12 12:18:49 -0800198 *
Guang Zhu6ffa7602012-02-07 19:14:02 -0800199 * @return the final command issued.
San Mehat67bd2cd2010-01-12 12:18:49 -0800200 */
Guang Zhu6ffa7602012-02-07 19:14:02 -0800201 private String sendCommandLocked(String cmd, Object... args)
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700202 throws NativeDaemonConnectorException {
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800203 // TODO: eventually enforce that cmd doesn't contain arguments
204 if (cmd.indexOf('\0') >= 0) {
205 throw new IllegalArgumentException("unexpected command: " + cmd);
Jeff Sharkeyb0aec072011-10-14 18:32:24 -0700206 }
207
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800208 final StringBuilder builder = new StringBuilder(cmd);
209 for (Object arg : args) {
210 final String argString = String.valueOf(arg);
211 if (argString.indexOf('\0') >= 0) {
212 throw new IllegalArgumentException("unexpected argument: " + arg);
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700213 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800214
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800215 builder.append(' ');
216 appendEscaped(builder, argString);
217 }
218
Guang Zhu6ffa7602012-02-07 19:14:02 -0800219 final String unterminated = builder.toString();
220 log("SND -> {" + unterminated + "}");
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800221
222 builder.append('\0');
223
Guang Zhu6ffa7602012-02-07 19:14:02 -0800224 if (mOutputStream == null) {
225 throw new NativeDaemonConnectorException("missing output stream");
226 } else {
227 try {
228 mOutputStream.write(builder.toString().getBytes(Charsets.UTF_8));
229 } catch (IOException e) {
230 throw new NativeDaemonConnectorException("problem sending command", e);
San Mehat67bd2cd2010-01-12 12:18:49 -0800231 }
232 }
233
Guang Zhu6ffa7602012-02-07 19:14:02 -0800234 return unterminated;
San Mehat67bd2cd2010-01-12 12:18:49 -0800235 }
San Mehatdeba6932010-01-20 15:14:31 -0800236
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700237 /**
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800238 * Issue the given command to the native daemon and return a single expected
239 * response.
240 *
241 * @throws NativeDaemonConnectorException when problem communicating with
242 * native daemon, or if the response matches
243 * {@link NativeDaemonEvent#isClassClientError()} or
244 * {@link NativeDaemonEvent#isClassServerError()}.
San Mehatdeba6932010-01-20 15:14:31 -0800245 */
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800246 public NativeDaemonEvent execute(Command cmd) throws NativeDaemonConnectorException {
247 return execute(cmd.mCmd, cmd.mArguments.toArray());
248 }
249
250 /**
251 * Issue the given command to the native daemon and return a single expected
252 * response.
253 *
254 * @throws NativeDaemonConnectorException when problem communicating with
255 * native daemon, or if the response matches
256 * {@link NativeDaemonEvent#isClassClientError()} or
257 * {@link NativeDaemonEvent#isClassServerError()}.
258 */
259 public NativeDaemonEvent execute(String cmd, Object... args)
260 throws NativeDaemonConnectorException {
261 final NativeDaemonEvent[] events = executeForList(cmd, args);
262 if (events.length != 1) {
263 throw new NativeDaemonConnectorException(
264 "Expected exactly one response, but received " + events.length);
265 }
266 return events[0];
267 }
268
269 /**
270 * Issue the given command to the native daemon and return any
271 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
272 * final terminal response.
273 *
274 * @throws NativeDaemonConnectorException when problem communicating with
275 * native daemon, or if the response matches
276 * {@link NativeDaemonEvent#isClassClientError()} or
277 * {@link NativeDaemonEvent#isClassServerError()}.
278 */
279 public NativeDaemonEvent[] executeForList(Command cmd) throws NativeDaemonConnectorException {
280 return executeForList(cmd.mCmd, cmd.mArguments.toArray());
281 }
282
283 /**
284 * Issue the given command to the native daemon and return any
285 * {@link NativeDaemonEvent#isClassContinue()} responses, including the
286 * final terminal response.
287 *
288 * @throws NativeDaemonConnectorException when problem communicating with
289 * native daemon, or if the response matches
290 * {@link NativeDaemonEvent#isClassClientError()} or
291 * {@link NativeDaemonEvent#isClassServerError()}.
292 */
293 public NativeDaemonEvent[] executeForList(String cmd, Object... args)
San Mehat4c27e0e2010-01-29 05:22:17 -0800294 throws NativeDaemonConnectorException {
Guang Zhu6ffa7602012-02-07 19:14:02 -0800295 synchronized (mDaemonLock) {
296 return executeLocked(cmd, args);
297 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800298 }
San Mehatdeba6932010-01-20 15:14:31 -0800299
Guang Zhu6ffa7602012-02-07 19:14:02 -0800300 private NativeDaemonEvent[] executeLocked(String cmd, Object... args)
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800301 throws NativeDaemonConnectorException {
302 final ArrayList<NativeDaemonEvent> events = Lists.newArrayList();
Guang Zhu6ffa7602012-02-07 19:14:02 -0800303
304 while (mResponseQueue.size() > 0) {
305 try {
306 log("ignoring {" + mResponseQueue.take() + "}");
307 } catch (Exception e) {}
308 }
309
310 final String sentCommand = sendCommandLocked(cmd, args);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800311
312 NativeDaemonEvent event = null;
313 do {
Guang Zhu6ffa7602012-02-07 19:14:02 -0800314 try {
315 event = mResponseQueue.take();
316 } catch (InterruptedException e) {
317 Slog.w(TAG, "interrupted waiting for event line");
318 continue;
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800319 }
320 events.add(event);
321 } while (event.isClassContinue());
322
323 if (event.isClassClientError()) {
Guang Zhu6ffa7602012-02-07 19:14:02 -0800324 throw new NativeDaemonArgumentException(sentCommand, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800325 }
326 if (event.isClassServerError()) {
Guang Zhu6ffa7602012-02-07 19:14:02 -0800327 throw new NativeDaemonFailureException(sentCommand, event);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800328 }
329
330 return events.toArray(new NativeDaemonEvent[events.size()]);
331 }
332
333 /**
334 * Issue a command to the native daemon and return the raw responses.
335 *
336 * @deprecated callers should move to {@link #execute(String, Object...)}
337 * which returns parsed {@link NativeDaemonEvent}.
338 */
339 @Deprecated
340 public ArrayList<String> doCommand(String cmd) throws NativeDaemonConnectorException {
341 final ArrayList<String> rawEvents = Lists.newArrayList();
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800342 final NativeDaemonEvent[] events = executeForList(cmd);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800343 for (NativeDaemonEvent event : events) {
344 rawEvents.add(event.getRawEvent());
345 }
346 return rawEvents;
347 }
348
349 /**
350 * Issues a list command and returns the cooked list of all
351 * {@link NativeDaemonEvent#getMessage()} which match requested code.
352 */
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800353 @Deprecated
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800354 public String[] doListCommand(String cmd, int expectedCode)
355 throws NativeDaemonConnectorException {
356 final ArrayList<String> list = Lists.newArrayList();
357
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800358 final NativeDaemonEvent[] events = executeForList(cmd);
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800359 for (int i = 0; i < events.length - 1; i++) {
360 final NativeDaemonEvent event = events[i];
361 final int code = event.getCode();
362 if (code == expectedCode) {
363 list.add(event.getMessage());
364 } else {
San Mehat4c27e0e2010-01-29 05:22:17 -0800365 throw new NativeDaemonConnectorException(
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800366 "unexpected list response " + code + " instead of " + expectedCode);
San Mehatdeba6932010-01-20 15:14:31 -0800367 }
368 }
Jeff Sharkey31c6e482011-11-18 17:09:01 -0800369
370 final NativeDaemonEvent finalEvent = events[events.length - 1];
371 if (!finalEvent.isClassOk()) {
372 throw new NativeDaemonConnectorException("unexpected final event: " + finalEvent);
373 }
374
375 return list.toArray(new String[list.size()]);
376 }
377
378 /**
379 * Append the given argument to {@link StringBuilder}, escaping as needed,
380 * and surrounding with quotes when it contains spaces.
381 */
382 // @VisibleForTesting
383 static void appendEscaped(StringBuilder builder, String arg) {
384 final boolean hasSpaces = arg.indexOf(' ') >= 0;
385 if (hasSpaces) {
386 builder.append('"');
387 }
388
389 final int length = arg.length();
390 for (int i = 0; i < length; i++) {
391 final char c = arg.charAt(i);
392
393 if (c == '"') {
394 builder.append("\\\"");
395 } else if (c == '\\') {
396 builder.append("\\\\");
397 } else {
398 builder.append(c);
399 }
400 }
401
402 if (hasSpaces) {
403 builder.append('"');
404 }
405 }
406
407 private static class NativeDaemonArgumentException extends NativeDaemonConnectorException {
408 public NativeDaemonArgumentException(String command, NativeDaemonEvent event) {
409 super(command, event);
410 }
411
412 @Override
413 public IllegalArgumentException rethrowAsParcelableException() {
414 throw new IllegalArgumentException(getMessage(), this);
415 }
416 }
417
418 private static class NativeDaemonFailureException extends NativeDaemonConnectorException {
419 public NativeDaemonFailureException(String command, NativeDaemonEvent event) {
420 super(command, event);
421 }
San Mehatdeba6932010-01-20 15:14:31 -0800422 }
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700423
Jeff Sharkeyba2896e2011-11-30 18:13:54 -0800424 /**
425 * Command builder that handles argument list building.
426 */
427 public static class Command {
428 private String mCmd;
429 private ArrayList<Object> mArguments = Lists.newArrayList();
430
431 public Command(String cmd, Object... args) {
432 mCmd = cmd;
433 for (Object arg : args) {
434 appendArg(arg);
435 }
436 }
437
438 public Command appendArg(Object arg) {
439 mArguments.add(arg);
440 return this;
441 }
442 }
443
Jeff Sharkeyfa23c5a2011-08-09 21:44:24 -0700444 /** {@inheritDoc} */
445 public void monitor() {
446 synchronized (mDaemonLock) { }
447 }
Robert Greenwalt470fd722012-01-18 12:51:15 -0800448
449 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
450 mLocalLog.dump(fd, pw, args);
451 }
452
453 private void log(String logstring) {
454 if (LOGD) Slog.d(TAG, logstring);
455 mLocalLog.log(logstring);
456 }
San Mehat67bd2cd2010-01-12 12:18:49 -0800457}