/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ddmuilib.logcat; import com.android.ddmlib.AndroidDebugBridge; import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener; import com.android.ddmlib.IDevice; import org.eclipse.jface.preference.IPreferenceStore; import java.util.HashMap; import java.util.Map; /** * A factory for {@link LogCatReceiver} objects. Its primary objective is to cache * constructed {@link LogCatReceiver}'s per device and hand them back when requested. */ public class LogCatReceiverFactory { /** Singleton instance. */ public static final LogCatReceiverFactory INSTANCE = new LogCatReceiverFactory(); private Map<String, LogCatReceiver> mReceiverCache = new HashMap<String, LogCatReceiver>(); /** Private constructor: cannot instantiate. */ private LogCatReceiverFactory() { AndroidDebugBridge.addDeviceChangeListener(new IDeviceChangeListener() { @Override public void deviceDisconnected(final IDevice device) { // The deviceDisconnected() is called from DDMS code that holds // multiple locks regarding list of clients, etc. // It so happens that #newReceiver() below adds a clientChangeListener // which requires those locks as well. So if we call // #removeReceiverFor from a DDMS/Monitor thread, we could end up // in a deadlock. As a result, we spawn a separate thread that // doesn't hold any of the DDMS locks to remove the receiver. Thread t = new Thread(new Runnable() { @Override public void run() { removeReceiverFor(device); } }, "Remove logcat receiver for " + device.getSerialNumber()); t.start(); } @Override public void deviceConnected(IDevice device) { } @Override public void deviceChanged(IDevice device, int changeMask) { } }); } /** * Remove existing logcat receivers. This method should not be called from a DDMS thread * context that might be holding locks. Doing so could result in a deadlock with the following * two threads locked up: <ul> * <li> {@link #removeReceiverFor(IDevice)} waiting to lock {@link LogCatReceiverFactory}, * while holding a DDMS monitor internal lock. </li> * <li> {@link #newReceiver(IDevice, IPreferenceStore)} holding {@link LogCatReceiverFactory} * while attempting to obtain a DDMS monitor lock. </li> * </ul> */ private synchronized void removeReceiverFor(IDevice device) { LogCatReceiver r = mReceiverCache.get(device.getSerialNumber()); if (r != null) { r.stop(); mReceiverCache.remove(device.getSerialNumber()); } } public synchronized LogCatReceiver newReceiver(IDevice device, IPreferenceStore prefs) { LogCatReceiver r = mReceiverCache.get(device.getSerialNumber()); if (r != null) { return r; } r = new LogCatReceiver(device, prefs); mReceiverCache.put(device.getSerialNumber(), r); return r; } }