/* * Copyright (C) 2014 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.tools.idea.ddms.adb; import com.android.ddmlib.*; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.wm.ToolWindow; import com.intellij.openapi.wm.ToolWindowManager; import org.jetbrains.android.actions.AndroidEnableAdbServiceAction; import org.jetbrains.android.logcat.AdbErrors; import org.jetbrains.android.logcat.AndroidToolWindowFactory; import org.jetbrains.android.util.AndroidUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.util.concurrent.CancellationException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; /** * {@link com.android.tools.idea.ddms.adb.AdbService} is the main entry point to initializing and obtaining the * {@link com.android.ddmlib.AndroidDebugBridge}. * * <p>Actions that require a handle to the debug bridge should invoke {@link #getDebugBridge(java.io.File)} to obtain * the debug bridge. This bridge is only valid at the time it is obtained, and could go stale in the future (e.g. user disables * adb integration via {@link org.jetbrains.android.actions.AndroidEnableAdbServiceAction}, or launches monitor via * {@link org.jetbrains.android.actions.AndroidRunDdmsAction}). * * <p>Components that need to keep a handle to the bridge for longer durations (such as tool windows that monitor device state) should do so * by first invoking {@link #getDebugBridge(java.io.File)} to obtain the bridge, and implementing * {@link com.android.ddmlib.AndroidDebugBridge.IDebugBridgeChangeListener} to ensure that they get updates to the status of the bridge. */ public class AdbService { @NotNull private static final Ddmlib ourDdmlib = new Ddmlib(); @Nullable private static SettableFuture<AndroidDebugBridge> ourFuture; @Nullable private static BridgeConnectorTask ourMonitorTask; public static synchronized ListenableFuture<AndroidDebugBridge> getDebugBridge(@NotNull File adb) { // Cancel previous requests if they were unsuccessful if (ourFuture != null && !wasSuccessful(ourFuture)) { terminateDdmlib(); } if (ourFuture == null) { ourFuture = SettableFuture.create(); ourMonitorTask = new BridgeConnectorTask(adb, ourDdmlib, ourFuture); ApplicationManager.getApplication().executeOnPooledThread(ourMonitorTask); } return ourFuture; } public static synchronized void terminateDdmlib() { ourFuture = null; if (ourMonitorTask != null) { ourMonitorTask.cancel(); } ourDdmlib.terminate(); } public static boolean canDdmsBeCorrupted(@NotNull AndroidDebugBridge bridge) { return isDdmsCorrupted(bridge) || allDevicesAreEmpty(bridge); } private static boolean allDevicesAreEmpty(@NotNull AndroidDebugBridge bridge) { for (IDevice device : bridge.getDevices()) { if (device.getClients().length > 0) { return false; } } return true; } public static boolean isDdmsCorrupted(@NotNull AndroidDebugBridge bridge) { // TODO: find other way to check if debug service is available IDevice[] devices = bridge.getDevices(); if (devices.length > 0) { for (IDevice device : devices) { Client[] clients = device.getClients(); if (clients.length > 0) { ClientData clientData = clients[0].getClientData(); return clientData.getVmIdentifier() == null; } } } return false; } public static synchronized void restartDdmlib(@NotNull Project project) { ToolWindow toolWindow = ToolWindowManager.getInstance(project).getToolWindow(AndroidToolWindowFactory.TOOL_WINDOW_ID); boolean hidden = false; if (toolWindow != null && toolWindow.isVisible()) { hidden = true; toolWindow.hide(null); } terminateDdmlib(); if (hidden) { toolWindow.show(null); } } /** Returns whether the future has completed successfully. */ private static boolean wasSuccessful(Future<AndroidDebugBridge> future) { if (!future.isDone()) { return false; } try { AndroidDebugBridge bridge = future.get(); return bridge != null && bridge.isConnected(); } catch (Exception e) { return false; } } private static class BridgeConnectorTask implements Runnable { private static final long TIMEOUT_MS = 10000; private final CountDownLatch myCancelLatch = new CountDownLatch(1); private final Ddmlib myDdmlib; private final SettableFuture<AndroidDebugBridge> myResult; private final File myAdb; public BridgeConnectorTask(File adb, @NotNull Ddmlib ddmlib, @NotNull SettableFuture<AndroidDebugBridge> result) { myAdb = adb; myDdmlib = ddmlib; myResult = result; } @Override public void run() { AdbErrors.clear(); myDdmlib.initialize(myAdb); long startTime = System.currentTimeMillis(); while (!myDdmlib.isConnected()) { // if not connected, wait for sometime, unless we were cancelled if (myDdmlib.isConnectionInProgress()) { try { if (myCancelLatch.await(200, TimeUnit.MILLISECONDS)) { break; } } catch (InterruptedException ignore) { break; } } // check if we should time out if (System.currentTimeMillis() > (startTime + TIMEOUT_MS)) { break; } } if (myDdmlib.isConnected()) { myResult.set(AndroidDebugBridge.getBridge()); } else { myResult.setException(new CancellationException()); } } public void cancel() { myCancelLatch.countDown(); } } /** Encapsulates DDM library initialization/termination*/ private static class Ddmlib { private static final Logger LOG = Logger.getInstance(Ddmlib.class); private AndroidDebugBridge myBridge; private boolean myDdmLibInitialized = false; private boolean ourDdmLibTerminated = false; public synchronized void initialize(@NotNull File adb) { boolean forceRestart = true; if (!myDdmLibInitialized) { myDdmLibInitialized = true; ourDdmLibTerminated = false; DdmPreferences.setLogLevel(Log.LogLevel.INFO.getStringValue()); DdmPreferences.setTimeOut(AndroidUtils.TIMEOUT); AndroidDebugBridge.init(AndroidEnableAdbServiceAction.isAdbServiceEnabled()); LOG.info("DDMLib initialized"); } else { final AndroidDebugBridge bridge = AndroidDebugBridge.getBridge(); forceRestart = bridge != null && !bridge.isConnected(); if (forceRestart) { LOG.info("Force restarting bridge: currently not connected."); } } myBridge = AndroidDebugBridge.createBridge(adb.getPath(), forceRestart); } public synchronized boolean isConnectionInProgress() { return !(isConnected() || ourDdmLibTerminated); } public synchronized boolean isConnected() { return myBridge.isConnected(); } public synchronized void terminate() { ourDdmLibTerminated = true; AndroidDebugBridge.disconnectBridge(); AndroidDebugBridge.terminate(); myDdmLibInitialized = false; LOG.info("DDMLib terminated"); } } }