1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php 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 17 package com.android.ide.eclipse.adt; 18 19 import static com.android.SdkConstants.CURRENT_PLATFORM; 20 import static com.android.SdkConstants.PLATFORM_DARWIN; 21 import static com.android.SdkConstants.PLATFORM_LINUX; 22 import static com.android.SdkConstants.PLATFORM_WINDOWS; 23 24 import com.android.SdkConstants; 25 import com.android.annotations.NonNull; 26 import com.android.annotations.Nullable; 27 import com.android.ide.common.resources.ResourceFile; 28 import com.android.ide.common.sdk.LoadStatus; 29 import com.android.ide.eclipse.adt.AdtPlugin.CheckSdkErrorHandler.Solution; 30 import com.android.ide.eclipse.adt.internal.VersionCheck; 31 import com.android.ide.eclipse.adt.internal.actions.SdkManagerAction; 32 import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor; 33 import com.android.ide.eclipse.adt.internal.editors.IconFactory; 34 import com.android.ide.eclipse.adt.internal.editors.common.CommonXmlEditor; 35 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.IncludeFinder; 36 import com.android.ide.eclipse.adt.internal.lint.LintDeltaProcessor; 37 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; 38 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs.BuildVerbosity; 39 import com.android.ide.eclipse.adt.internal.project.AndroidClasspathContainerInitializer; 40 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; 41 import com.android.ide.eclipse.adt.internal.project.ProjectHelper; 42 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor; 43 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener; 44 import com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IProjectListener; 45 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; 46 import com.android.ide.eclipse.adt.internal.sdk.Sdk; 47 import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener; 48 import com.android.ide.eclipse.adt.internal.ui.EclipseUiHelper; 49 import com.android.ide.eclipse.ddms.DdmsPlugin; 50 import com.android.io.StreamException; 51 import com.android.resources.ResourceFolderType; 52 import com.android.sdklib.IAndroidTarget; 53 import com.android.utils.ILogger; 54 import com.google.common.io.Closeables; 55 56 import org.eclipse.core.commands.Command; 57 import org.eclipse.core.resources.IFile; 58 import org.eclipse.core.resources.IMarkerDelta; 59 import org.eclipse.core.resources.IProject; 60 import org.eclipse.core.resources.IResourceDelta; 61 import org.eclipse.core.resources.IWorkspace; 62 import org.eclipse.core.resources.ResourcesPlugin; 63 import org.eclipse.core.runtime.CoreException; 64 import org.eclipse.core.runtime.IPath; 65 import org.eclipse.core.runtime.IProgressMonitor; 66 import org.eclipse.core.runtime.IStatus; 67 import org.eclipse.core.runtime.QualifiedName; 68 import org.eclipse.core.runtime.Status; 69 import org.eclipse.core.runtime.SubMonitor; 70 import org.eclipse.core.runtime.jobs.Job; 71 import org.eclipse.jdt.core.IJavaElement; 72 import org.eclipse.jdt.core.IJavaProject; 73 import org.eclipse.jdt.core.JavaCore; 74 import org.eclipse.jdt.ui.JavaUI; 75 import org.eclipse.jface.dialogs.IDialogConstants; 76 import org.eclipse.jface.dialogs.MessageDialog; 77 import org.eclipse.jface.preference.IPreferenceStore; 78 import org.eclipse.jface.preference.PreferenceDialog; 79 import org.eclipse.jface.resource.ImageDescriptor; 80 import org.eclipse.jface.text.IRegion; 81 import org.eclipse.jface.util.IPropertyChangeListener; 82 import org.eclipse.jface.util.PropertyChangeEvent; 83 import org.eclipse.swt.graphics.Color; 84 import org.eclipse.swt.graphics.Image; 85 import org.eclipse.swt.widgets.Display; 86 import org.eclipse.swt.widgets.Shell; 87 import org.eclipse.ui.IEditorDescriptor; 88 import org.eclipse.ui.IEditorPart; 89 import org.eclipse.ui.IWorkbench; 90 import org.eclipse.ui.IWorkbenchPage; 91 import org.eclipse.ui.PartInitException; 92 import org.eclipse.ui.PlatformUI; 93 import org.eclipse.ui.browser.IWebBrowser; 94 import org.eclipse.ui.browser.IWorkbenchBrowserSupport; 95 import org.eclipse.ui.commands.ICommandService; 96 import org.eclipse.ui.console.ConsolePlugin; 97 import org.eclipse.ui.console.IConsole; 98 import org.eclipse.ui.console.IConsoleConstants; 99 import org.eclipse.ui.console.MessageConsole; 100 import org.eclipse.ui.console.MessageConsoleStream; 101 import org.eclipse.ui.dialogs.PreferencesUtil; 102 import org.eclipse.ui.handlers.IHandlerService; 103 import org.eclipse.ui.ide.IDE; 104 import org.eclipse.ui.plugin.AbstractUIPlugin; 105 import org.eclipse.ui.texteditor.AbstractTextEditor; 106 import org.eclipse.wb.internal.core.DesignerPlugin; 107 import org.osgi.framework.Bundle; 108 import org.osgi.framework.BundleContext; 109 110 import java.io.BufferedInputStream; 111 import java.io.BufferedReader; 112 import java.io.File; 113 import java.io.FileNotFoundException; 114 import java.io.FileReader; 115 import java.io.FileWriter; 116 import java.io.IOException; 117 import java.io.InputStream; 118 import java.io.InputStreamReader; 119 import java.io.OutputStream; 120 import java.io.Reader; 121 import java.net.MalformedURLException; 122 import java.net.URL; 123 import java.util.ArrayList; 124 import java.util.Arrays; 125 import java.util.List; 126 127 /** 128 * The activator class controls the plug-in life cycle 129 */ 130 public class AdtPlugin extends AbstractUIPlugin implements ILogger { 131 /** The plug-in ID */ 132 public static final String PLUGIN_ID = "com.android.ide.eclipse.adt"; //$NON-NLS-1$ 133 134 /** singleton instance */ 135 private static AdtPlugin sPlugin; 136 137 private static Image sAndroidLogo; 138 private static ImageDescriptor sAndroidLogoDesc; 139 140 /** The global android console */ 141 private MessageConsole mAndroidConsole; 142 143 /** Stream to write in the android console */ 144 private MessageConsoleStream mAndroidConsoleStream; 145 146 /** Stream to write error messages to the android console */ 147 private MessageConsoleStream mAndroidConsoleErrorStream; 148 149 /** Color used in the error console */ 150 private Color mRed; 151 152 /** Load status of the SDK. Any access MUST be in a synchronized(mPostLoadProjects) block */ 153 private LoadStatus mSdkLoadedStatus = LoadStatus.LOADING; 154 /** Project to update once the SDK is loaded. 155 * Any access MUST be in a synchronized(mPostLoadProjectsToResolve) block */ 156 private final ArrayList<IJavaProject> mPostLoadProjectsToResolve = 157 new ArrayList<IJavaProject>(); 158 /** Project to check validity of cache vs actual once the SDK is loaded. 159 * Any access MUST be in a synchronized(mPostLoadProjectsToResolve) block */ 160 private final ArrayList<IJavaProject> mPostLoadProjectsToCheck = new ArrayList<IJavaProject>(); 161 162 private GlobalProjectMonitor mResourceMonitor; 163 private ArrayList<ITargetChangeListener> mTargetChangeListeners = 164 new ArrayList<ITargetChangeListener>(); 165 166 /** 167 * This variable indicates that the job inside parseSdkContent() is currently 168 * trying to load the SDK, to avoid re-entrance. 169 * To check whether this succeeds or not, please see {@link #getSdkLoadStatus()}. 170 */ 171 private volatile boolean mParseSdkContentIsRunning; 172 173 /** 174 * An error handler for checkSdkLocationAndId() that will handle the generated error 175 * or warning message. Each method must return a boolean that will in turn be returned by 176 * checkSdkLocationAndId. 177 */ 178 public static abstract class CheckSdkErrorHandler { 179 180 public enum Solution { 181 NONE, 182 OPEN_SDK_MANAGER, 183 OPEN_ANDROID_PREFS, 184 OPEN_P2_UPDATE 185 } 186 187 /** 188 * Handle an error message during sdk location check. Returns whatever 189 * checkSdkLocationAndId() should returns. 190 */ 191 public abstract boolean handleError(Solution solution, String message); 192 193 /** 194 * Handle a warning message during sdk location check. Returns whatever 195 * checkSdkLocationAndId() should returns. 196 */ 197 public abstract boolean handleWarning(Solution solution, String message); 198 } 199 200 /** 201 * The constructor 202 */ 203 public AdtPlugin() { 204 sPlugin = this; 205 } 206 207 /* 208 * (non-Javadoc) 209 * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext) 210 */ 211 @Override 212 public void start(BundleContext context) throws Exception { 213 super.start(context); 214 215 // set the default android console. 216 mAndroidConsole = new MessageConsole("Android", null); //$NON-NLS-1$ 217 ConsolePlugin.getDefault().getConsoleManager().addConsoles( 218 new IConsole[] { mAndroidConsole }); 219 220 // get the stream to write in the android console. 221 mAndroidConsoleStream = mAndroidConsole.newMessageStream(); 222 mAndroidConsoleErrorStream = mAndroidConsole.newMessageStream(); 223 224 // get the eclipse store 225 IPreferenceStore eclipseStore = getPreferenceStore(); 226 AdtPrefs.init(eclipseStore); 227 228 // set the listener for the preference change 229 eclipseStore.addPropertyChangeListener(new IPropertyChangeListener() { 230 @Override 231 public void propertyChange(PropertyChangeEvent event) { 232 // load the new preferences 233 AdtPrefs.getPrefs().loadValues(event); 234 235 // if the SDK changed, we have to do some extra work 236 if (AdtPrefs.PREFS_SDK_DIR.equals(event.getProperty())) { 237 238 // finally restart adb, in case it's a different version 239 DdmsPlugin.setToolsLocation(getOsAbsoluteAdb(), true /* startAdb */, 240 getOsAbsoluteHprofConv(), getOsAbsoluteTraceview()); 241 242 // get the SDK location and build id. 243 if (checkSdkLocationAndId()) { 244 // if sdk if valid, reparse it 245 246 reparseSdk(); 247 } 248 } 249 } 250 }); 251 252 // load preferences. 253 AdtPrefs.getPrefs().loadValues(null /*event*/); 254 255 // initialize property-sheet library 256 DesignerPlugin.initialize( 257 this, 258 PLUGIN_ID, 259 CURRENT_PLATFORM == PLATFORM_WINDOWS, 260 CURRENT_PLATFORM == PLATFORM_DARWIN, 261 CURRENT_PLATFORM == PLATFORM_LINUX); 262 263 // initialize editors 264 startEditors(); 265 266 // Listen on resource file edits for updates to file inclusion 267 IncludeFinder.start(); 268 } 269 270 /* 271 * (non-Javadoc) 272 * 273 * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) 274 */ 275 @Override 276 public void stop(BundleContext context) throws Exception { 277 super.stop(context); 278 279 stopEditors(); 280 IncludeFinder.stop(); 281 282 DesignerPlugin.dispose(); 283 284 if (mRed != null) { 285 mRed.dispose(); 286 mRed = null; 287 } 288 289 synchronized (AdtPlugin.class) { 290 sPlugin = null; 291 } 292 } 293 294 /** Called when the workbench has been started */ 295 public void workbenchStarted() { 296 // Parse the SDK content. 297 // This is deferred in separate jobs to avoid blocking the bundle start. 298 final boolean isSdkLocationValid = checkSdkLocationAndId(); 299 if (isSdkLocationValid) { 300 // parse the SDK resources. 301 // Wait 2 seconds before starting the job. This leaves some time to the 302 // other bundles to initialize. 303 parseSdkContent(2000 /*milliseconds*/); 304 } 305 306 Display display = getDisplay(); 307 mRed = new Color(display, 0xFF, 0x00, 0x00); 308 309 // because this can be run, in some cases, by a non ui thread, and because 310 // changing the console properties update the ui, we need to make this change 311 // in the ui thread. 312 display.asyncExec(new Runnable() { 313 @Override 314 public void run() { 315 mAndroidConsoleErrorStream.setColor(mRed); 316 } 317 }); 318 } 319 320 /** 321 * Returns the shared instance 322 * 323 * @return the shared instance 324 */ 325 public static synchronized AdtPlugin getDefault() { 326 return sPlugin; 327 } 328 329 /** 330 * Returns the current display, if any 331 * 332 * @return the display 333 */ 334 @NonNull 335 public static Display getDisplay() { 336 synchronized (AdtPlugin.class) { 337 if (sPlugin != null) { 338 IWorkbench bench = sPlugin.getWorkbench(); 339 if (bench != null) { 340 Display display = bench.getDisplay(); 341 if (display != null) { 342 return display; 343 } 344 } 345 } 346 } 347 348 Display display = Display.getCurrent(); 349 if (display != null) { 350 return display; 351 } 352 353 return Display.getDefault(); 354 } 355 356 /** 357 * Returns the shell, if any 358 * 359 * @return the shell, if any 360 */ 361 @Nullable 362 public static Shell getShell() { 363 Display display = AdtPlugin.getDisplay(); 364 Shell shell = display.getActiveShell(); 365 if (shell == null) { 366 Shell[] shells = display.getShells(); 367 if (shells.length > 0) { 368 shell = shells[0]; 369 } 370 } 371 372 return shell; 373 } 374 375 /** Returns the adb path relative to the sdk folder */ 376 public static String getOsRelativeAdb() { 377 return SdkConstants.OS_SDK_PLATFORM_TOOLS_FOLDER + SdkConstants.FN_ADB; 378 } 379 380 /** Returns the zipalign path relative to the sdk folder */ 381 public static String getOsRelativeZipAlign() { 382 return SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_ZIPALIGN; 383 } 384 385 /** Returns the emulator path relative to the sdk folder */ 386 public static String getOsRelativeEmulator() { 387 return SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_EMULATOR; 388 } 389 390 /** Returns the adb path relative to the sdk folder */ 391 public static String getOsRelativeProguard() { 392 return SdkConstants.OS_SDK_TOOLS_PROGUARD_BIN_FOLDER + SdkConstants.FN_PROGUARD; 393 } 394 395 /** Returns the absolute adb path */ 396 public static String getOsAbsoluteAdb() { 397 return getOsSdkFolder() + getOsRelativeAdb(); 398 } 399 400 /** Returns the absolute zipalign path */ 401 public static String getOsAbsoluteZipAlign() { 402 return getOsSdkFolder() + getOsRelativeZipAlign(); 403 } 404 405 /** Returns the absolute traceview path */ 406 public static String getOsAbsoluteTraceview() { 407 return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER + 408 AdtConstants.FN_TRACEVIEW; 409 } 410 411 /** Returns the absolute emulator path */ 412 public static String getOsAbsoluteEmulator() { 413 return getOsSdkFolder() + getOsRelativeEmulator(); 414 } 415 416 public static String getOsAbsoluteHprofConv() { 417 return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER + 418 AdtConstants.FN_HPROF_CONV; 419 } 420 421 /** Returns the absolute proguard path */ 422 public static String getOsAbsoluteProguard() { 423 return getOsSdkFolder() + getOsRelativeProguard(); 424 } 425 426 /** 427 * Returns a Url file path to the javaDoc folder. 428 */ 429 public static String getUrlDoc() { 430 return ProjectHelper.getJavaDocPath( 431 getOsSdkFolder() + AdtConstants.WS_JAVADOC_FOLDER_LEAF); 432 } 433 434 /** 435 * Returns the SDK folder. 436 * Guaranteed to be terminated by a platform-specific path separator. 437 */ 438 public static synchronized String getOsSdkFolder() { 439 if (sPlugin == null) { 440 return null; 441 } 442 443 return AdtPrefs.getPrefs().getOsSdkFolder(); 444 } 445 446 public static String getOsSdkToolsFolder() { 447 return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER; 448 } 449 450 /** 451 * Returns an image descriptor for the image file at the given 452 * plug-in relative path 453 * 454 * @param path the path 455 * @return the image descriptor 456 */ 457 public static ImageDescriptor getImageDescriptor(String path) { 458 return imageDescriptorFromPlugin(PLUGIN_ID, path); 459 } 460 461 /** 462 * Reads the contents of an {@link IFile} and return it as a String 463 * 464 * @param file the file to be read 465 * @return the String read from the file, or null if there was an error 466 */ 467 @SuppressWarnings("resource") // Eclipse doesn't understand Closeables.closeQuietly yet 468 @Nullable 469 public static String readFile(@NonNull IFile file) { 470 InputStream contents = null; 471 InputStreamReader reader = null; 472 try { 473 contents = file.getContents(); 474 String charset = file.getCharset(); 475 reader = new InputStreamReader(contents, charset); 476 return readFile(reader); 477 } catch (CoreException e) { 478 // pass -- ignore files we can't read 479 } catch (IOException e) { 480 // pass -- ignore files we can't read. 481 482 // Note that IFile.getContents() indicates it throws a CoreException but 483 // experience shows that if the file does not exists it really throws 484 // IOException. 485 // New InputStreamReader() throws UnsupportedEncodingException 486 // which is handled by this IOException catch. 487 488 } finally { 489 Closeables.closeQuietly(reader); 490 Closeables.closeQuietly(contents); 491 } 492 493 return null; 494 } 495 496 /** 497 * Reads the contents of an {@link File} and return it as a String 498 * 499 * @param file the file to be read 500 * @return the String read from the file, or null if there was an error 501 */ 502 public static String readFile(File file) { 503 try { 504 return readFile(new FileReader(file)); 505 } catch (FileNotFoundException e) { 506 AdtPlugin.log(e, "Can't read file %1$s", file); //$NON-NLS-1$ 507 } 508 509 return null; 510 } 511 512 /** 513 * Writes the given content out to the given {@link File}. The file will be deleted if 514 * it already exists. 515 * 516 * @param file the target file 517 * @param content the content to be written into the file 518 */ 519 public static void writeFile(File file, String content) { 520 if (file.exists()) { 521 file.delete(); 522 } 523 FileWriter fw = null; 524 try { 525 fw = new FileWriter(file); 526 fw.write(content); 527 } catch (IOException e) { 528 AdtPlugin.log(e, null); 529 } finally { 530 if (fw != null) { 531 try { 532 fw.close(); 533 } catch (IOException e) { 534 AdtPlugin.log(e, null); 535 } 536 } 537 } 538 } 539 540 /** 541 * Returns true iff the given file contains the given String. 542 * 543 * @param file the file to look for the string in 544 * @param string the string to be searched for 545 * @return true if the file is found and contains the given string anywhere within it 546 */ 547 @SuppressWarnings("resource") // Closed by streamContains 548 public static boolean fileContains(IFile file, String string) { 549 InputStream contents = null; 550 try { 551 contents = file.getContents(); 552 String charset = file.getCharset(); 553 return streamContains(new InputStreamReader(contents, charset), string); 554 } catch (Exception e) { 555 AdtPlugin.log(e, "Can't read file %1$s", file); //$NON-NLS-1$ 556 } 557 558 return false; 559 } 560 561 /** 562 * Returns true iff the given file contains the given String. 563 * 564 * @param file the file to look for the string in 565 * @param string the string to be searched for 566 * @return true if the file is found and contains the given string anywhere within it 567 */ 568 public static boolean fileContains(File file, String string) { 569 try { 570 return streamContains(new FileReader(file), string); 571 } catch (Exception e) { 572 AdtPlugin.log(e, "Can't read file %1$s", file); //$NON-NLS-1$ 573 } 574 575 return false; 576 } 577 578 /** 579 * Returns true iff the given input stream contains the given String. 580 * 581 * @param r the stream to look for the string in 582 * @param string the string to be searched for 583 * @return true if the file is found and contains the given string anywhere within it 584 */ 585 public static boolean streamContains(Reader r, String string) { 586 if (string.length() == 0) { 587 return true; 588 } 589 590 PushbackReader reader = null; 591 try { 592 reader = new PushbackReader(r, string.length()); 593 char first = string.charAt(0); 594 while (true) { 595 int c = reader.read(); 596 if (c == -1) { 597 return false; 598 } else if (c == first) { 599 boolean matches = true; 600 for (int i = 1; i < string.length(); i++) { 601 c = reader.read(); 602 if (c == -1) { 603 return false; 604 } else if (string.charAt(i) != (char)c) { 605 matches = false; 606 // Back up the characters that did not match 607 reader.backup(i-1); 608 break; 609 } 610 } 611 if (matches) { 612 return true; 613 } 614 } 615 } 616 } catch (Exception e) { 617 AdtPlugin.log(e, "Can't read stream"); //$NON-NLS-1$ 618 } finally { 619 try { 620 if (reader != null) { 621 reader.close(); 622 } 623 } catch (IOException e) { 624 AdtPlugin.log(e, "Can't read stream"); //$NON-NLS-1$ 625 } 626 } 627 628 return false; 629 630 } 631 632 /** 633 * A special reader that allows backing up in the input (up to a predefined maximum 634 * number of characters) 635 * <p> 636 * NOTE: This class ONLY works with the {@link #read()} method!! 637 */ 638 private static class PushbackReader extends BufferedReader { 639 /** 640 * Rolling/circular buffer. Can be a char rather than int since we never store EOF 641 * in it. 642 */ 643 private char[] mStorage; 644 645 /** Points to the head of the queue. When equal to the tail, the queue is empty. */ 646 private int mHead; 647 648 /** 649 * Points to the tail of the queue. This will move with each read of the actual 650 * wrapped reader, and the characters previous to it in the circular buffer are 651 * the most recently read characters. 652 */ 653 private int mTail; 654 655 /** 656 * Creates a new reader with a given maximum number of backup characters 657 * 658 * @param reader the reader to wrap 659 * @param max the maximum number of characters to allow rollback for 660 */ 661 public PushbackReader(Reader reader, int max) { 662 super(reader); 663 mStorage = new char[max + 1]; 664 } 665 666 @Override 667 public int read() throws IOException { 668 // Have we backed up? If so we should serve characters 669 // from the storage 670 if (mHead != mTail) { 671 char c = mStorage[mHead]; 672 mHead = (mHead + 1) % mStorage.length; 673 return c; 674 } 675 assert mHead == mTail; 676 677 // No backup -- read the next character, but stash it into storage 678 // as well such that we can retrieve it if we must. 679 int c = super.read(); 680 mStorage[mHead] = (char) c; 681 mHead = mTail = (mHead + 1) % mStorage.length; 682 return c; 683 } 684 685 /** 686 * Backs up the reader a given number of characters. The next N reads will yield 687 * the N most recently read characters prior to this backup. 688 * 689 * @param n the number of characters to be backed up 690 */ 691 public void backup(int n) { 692 if (n >= mStorage.length) { 693 throw new IllegalArgumentException("Exceeded backup limit"); 694 } 695 assert n < mStorage.length; 696 mHead -= n; 697 if (mHead < 0) { 698 mHead += mStorage.length; 699 } 700 } 701 } 702 703 /** 704 * Reads the contents of a {@link ResourceFile} and returns it as a String 705 * 706 * @param file the file to be read 707 * @return the contents as a String, or null if reading failed 708 */ 709 public static String readFile(ResourceFile file) { 710 InputStream contents = null; 711 try { 712 contents = file.getFile().getContents(); 713 return readFile(new InputStreamReader(contents)); 714 } catch (StreamException e) { 715 // pass -- ignore files we can't read 716 } finally { 717 try { 718 if (contents != null) { 719 contents.close(); 720 } 721 } catch (IOException e) { 722 AdtPlugin.log(e, "Can't read layout file"); //$NON-NLS-1$ 723 } 724 } 725 726 return null; 727 } 728 729 /** 730 * Reads the contents of a {@link Reader} and return it as a String. This 731 * method will close the input reader. 732 * 733 * @param reader the reader to be read from 734 * @return the String read from reader, or null if there was an error 735 */ 736 public static String readFile(Reader reader) { 737 BufferedReader bufferedReader = null; 738 try { 739 bufferedReader = new BufferedReader(reader); 740 StringBuilder sb = new StringBuilder(2000); 741 while (true) { 742 int c = bufferedReader.read(); 743 if (c == -1) { 744 return sb.toString(); 745 } else { 746 sb.append((char)c); 747 } 748 } 749 } catch (IOException e) { 750 // pass -- ignore files we can't read 751 } finally { 752 try { 753 if (bufferedReader != null) { 754 bufferedReader.close(); 755 } 756 } catch (IOException e) { 757 AdtPlugin.log(e, "Can't read input stream"); //$NON-NLS-1$ 758 } 759 } 760 761 return null; 762 } 763 764 /** 765 * Reads and returns the content of a text file embedded in the plugin jar 766 * file. 767 * @param filepath the file path to the text file 768 * @return null if the file could not be read 769 */ 770 public static String readEmbeddedTextFile(String filepath) { 771 try { 772 InputStream is = readEmbeddedFileAsStream(filepath); 773 if (is != null) { 774 BufferedReader reader = new BufferedReader(new InputStreamReader(is)); 775 try { 776 String line; 777 StringBuilder total = new StringBuilder(reader.readLine()); 778 while ((line = reader.readLine()) != null) { 779 total.append('\n'); 780 total.append(line); 781 } 782 783 return total.toString(); 784 } finally { 785 reader.close(); 786 } 787 } 788 } catch (IOException e) { 789 // we'll just return null 790 AdtPlugin.log(e, "Failed to read text file '%s'", filepath); //$NON-NLS-1$ 791 } 792 793 return null; 794 } 795 796 /** 797 * Reads and returns the content of a binary file embedded in the plugin jar 798 * file. 799 * @param filepath the file path to the text file 800 * @return null if the file could not be read 801 */ 802 public static byte[] readEmbeddedFile(String filepath) { 803 try { 804 InputStream is = readEmbeddedFileAsStream(filepath); 805 if (is != null) { 806 // create a buffered reader to facilitate reading. 807 BufferedInputStream stream = new BufferedInputStream(is); 808 try { 809 // get the size to read. 810 int avail = stream.available(); 811 812 // create the buffer and reads it. 813 byte[] buffer = new byte[avail]; 814 stream.read(buffer); 815 816 // and return. 817 return buffer; 818 } finally { 819 stream.close(); 820 } 821 } 822 } catch (IOException e) { 823 // we'll just return null;. 824 AdtPlugin.log(e, "Failed to read binary file '%s'", filepath); //$NON-NLS-1$ 825 } 826 827 return null; 828 } 829 830 /** 831 * Reads and returns the content of a binary file embedded in the plugin jar 832 * file. 833 * @param filepath the file path to the text file 834 * @return null if the file could not be read 835 */ 836 public static InputStream readEmbeddedFileAsStream(String filepath) { 837 // attempt to read an embedded file 838 try { 839 URL url = getEmbeddedFileUrl(AdtConstants.WS_SEP + filepath); 840 if (url != null) { 841 return url.openStream(); 842 } 843 } catch (MalformedURLException e) { 844 // we'll just return null. 845 AdtPlugin.log(e, "Failed to read stream '%s'", filepath); //$NON-NLS-1$ 846 } catch (IOException e) { 847 // we'll just return null;. 848 AdtPlugin.log(e, "Failed to read stream '%s'", filepath); //$NON-NLS-1$ 849 } 850 851 return null; 852 } 853 854 /** 855 * Returns the URL of a binary file embedded in the plugin jar file. 856 * @param filepath the file path to the text file 857 * @return null if the file was not found. 858 */ 859 public static URL getEmbeddedFileUrl(String filepath) { 860 Bundle bundle = null; 861 synchronized (AdtPlugin.class) { 862 if (sPlugin != null) { 863 bundle = sPlugin.getBundle(); 864 } else { 865 AdtPlugin.log(IStatus.WARNING, "ADT Plugin is missing"); //$NON-NLS-1$ 866 return null; 867 } 868 } 869 870 // attempt to get a file to one of the template. 871 String path = filepath; 872 if (!path.startsWith(AdtConstants.WS_SEP)) { 873 path = AdtConstants.WS_SEP + path; 874 } 875 876 URL url = bundle.getEntry(path); 877 878 if (url == null) { 879 AdtPlugin.log(IStatus.INFO, "Bundle file URL not found at path '%s'", path); //$NON-NLS-1$ 880 } 881 882 return url; 883 } 884 885 /** 886 * Displays an error dialog box. This dialog box is ran asynchronously in the ui thread, 887 * therefore this method can be called from any thread. 888 * @param title The title of the dialog box 889 * @param message The error message 890 */ 891 public final static void displayError(final String title, final String message) { 892 // get the current Display 893 final Display display = getDisplay(); 894 895 // dialog box only run in ui thread.. 896 display.asyncExec(new Runnable() { 897 @Override 898 public void run() { 899 Shell shell = display.getActiveShell(); 900 MessageDialog.openError(shell, title, message); 901 } 902 }); 903 } 904 905 /** 906 * Displays a warning dialog box. This dialog box is ran asynchronously in the ui thread, 907 * therefore this method can be called from any thread. 908 * @param title The title of the dialog box 909 * @param message The warning message 910 */ 911 public final static void displayWarning(final String title, final String message) { 912 // get the current Display 913 final Display display = getDisplay(); 914 915 // dialog box only run in ui thread.. 916 display.asyncExec(new Runnable() { 917 @Override 918 public void run() { 919 Shell shell = display.getActiveShell(); 920 MessageDialog.openWarning(shell, title, message); 921 } 922 }); 923 } 924 925 /** 926 * Display a yes/no question dialog box. This dialog is opened synchronously in the ui thread, 927 * therefore this message can be called from any thread. 928 * @param title The title of the dialog box 929 * @param message The error message 930 * @return true if OK was clicked. 931 */ 932 public final static boolean displayPrompt(final String title, final String message) { 933 // get the current Display and Shell 934 final Display display = getDisplay(); 935 936 // we need to ask the user what he wants to do. 937 final boolean[] result = new boolean[1]; 938 display.syncExec(new Runnable() { 939 @Override 940 public void run() { 941 Shell shell = display.getActiveShell(); 942 result[0] = MessageDialog.openQuestion(shell, title, message); 943 } 944 }); 945 return result[0]; 946 } 947 948 /** 949 * Logs a message to the default Eclipse log. 950 * 951 * @param severity The severity code. Valid values are: {@link IStatus#OK}, 952 * {@link IStatus#ERROR}, {@link IStatus#INFO}, {@link IStatus#WARNING} or 953 * {@link IStatus#CANCEL}. 954 * @param format The format string, like for {@link String#format(String, Object...)}. 955 * @param args The arguments for the format string, like for 956 * {@link String#format(String, Object...)}. 957 */ 958 public static void log(int severity, String format, Object ... args) { 959 if (format == null) { 960 return; 961 } 962 963 String message = String.format(format, args); 964 Status status = new Status(severity, PLUGIN_ID, message); 965 966 if (getDefault() != null) { 967 getDefault().getLog().log(status); 968 } else { 969 // During UnitTests, we generally don't have a plugin object. It's ok 970 // to log to stdout or stderr in this case. 971 (severity < IStatus.ERROR ? System.out : System.err).println(status.toString()); 972 } 973 } 974 975 /** 976 * Logs an exception to the default Eclipse log. 977 * <p/> 978 * The status severity is always set to ERROR. 979 * 980 * @param exception the exception to log. 981 * @param format The format string, like for {@link String#format(String, Object...)}. 982 * @param args The arguments for the format string, like for 983 * {@link String#format(String, Object...)}. 984 */ 985 public static void log(Throwable exception, String format, Object ... args) { 986 String message = null; 987 if (format != null) { 988 message = String.format(format, args); 989 } else { 990 message = ""; 991 } 992 Status status = new Status(IStatus.ERROR, PLUGIN_ID, message, exception); 993 994 if (getDefault() != null) { 995 getDefault().getLog().log(status); 996 } else { 997 // During UnitTests, we generally don't have a plugin object. It's ok 998 // to log to stderr in this case. 999 System.err.println(status.toString()); 1000 } 1001 } 1002 1003 /** 1004 * This is a mix between log(Throwable) and printErrorToConsole. 1005 * <p/> 1006 * This logs the exception with an ERROR severity and the given printf-like format message. 1007 * The same message is then printed on the Android error console with the associated tag. 1008 * 1009 * @param exception the exception to log. 1010 * @param format The format string, like for {@link String#format(String, Object...)}. 1011 * @param args The arguments for the format string, like for 1012 * {@link String#format(String, Object...)}. 1013 */ 1014 public static synchronized void logAndPrintError(Throwable exception, String tag, 1015 String format, Object ... args) { 1016 if (sPlugin != null) { 1017 String message = String.format(format, args); 1018 Status status = new Status(IStatus.ERROR, PLUGIN_ID, message, exception); 1019 getDefault().getLog().log(status); 1020 printToStream(sPlugin.mAndroidConsoleErrorStream, tag, message); 1021 showAndroidConsole(); 1022 } 1023 } 1024 1025 /** 1026 * Prints one or more error message to the android console. 1027 * @param tag A tag to be associated with the message. Can be null. 1028 * @param objects the objects to print through their <code>toString</code> method. 1029 */ 1030 public static synchronized void printErrorToConsole(String tag, Object... objects) { 1031 if (sPlugin != null) { 1032 printToStream(sPlugin.mAndroidConsoleErrorStream, tag, objects); 1033 1034 showAndroidConsole(); 1035 } 1036 } 1037 1038 /** 1039 * Prints one or more error message to the android console. 1040 * @param objects the objects to print through their <code>toString</code> method. 1041 */ 1042 public static void printErrorToConsole(Object... objects) { 1043 printErrorToConsole((String)null, objects); 1044 } 1045 1046 /** 1047 * Prints one or more error message to the android console. 1048 * @param project The project to which the message is associated. Can be null. 1049 * @param objects the objects to print through their <code>toString</code> method. 1050 */ 1051 public static void printErrorToConsole(IProject project, Object... objects) { 1052 String tag = project != null ? project.getName() : null; 1053 printErrorToConsole(tag, objects); 1054 } 1055 1056 /** 1057 * Prints one or more build messages to the android console, filtered by Build output verbosity. 1058 * @param level {@link BuildVerbosity} level of the message. 1059 * @param project The project to which the message is associated. Can be null. 1060 * @param objects the objects to print through their <code>toString</code> method. 1061 * @see BuildVerbosity#ALWAYS 1062 * @see BuildVerbosity#NORMAL 1063 * @see BuildVerbosity#VERBOSE 1064 */ 1065 public static synchronized void printBuildToConsole(BuildVerbosity level, IProject project, 1066 Object... objects) { 1067 if (sPlugin != null) { 1068 if (level.getLevel() <= AdtPrefs.getPrefs().getBuildVerbosity().getLevel()) { 1069 String tag = project != null ? project.getName() : null; 1070 printToStream(sPlugin.mAndroidConsoleStream, tag, objects); 1071 } 1072 } 1073 } 1074 1075 /** 1076 * Prints one or more message to the android console. 1077 * @param tag The tag to be associated with the message. Can be null. 1078 * @param objects the objects to print through their <code>toString</code> method. 1079 */ 1080 public static synchronized void printToConsole(String tag, Object... objects) { 1081 if (sPlugin != null) { 1082 printToStream(sPlugin.mAndroidConsoleStream, tag, objects); 1083 } 1084 } 1085 1086 /** 1087 * Prints one or more message to the android console. 1088 * @param project The project to which the message is associated. Can be null. 1089 * @param objects the objects to print through their <code>toString</code> method. 1090 */ 1091 public static void printToConsole(IProject project, Object... objects) { 1092 String tag = project != null ? project.getName() : null; 1093 printToConsole(tag, objects); 1094 } 1095 1096 /** Force the display of the android console */ 1097 public static void showAndroidConsole() { 1098 // first make sure the console is in the workbench 1099 EclipseUiHelper.showView(IConsoleConstants.ID_CONSOLE_VIEW, true); 1100 1101 // now make sure it's not docked. 1102 ConsolePlugin.getDefault().getConsoleManager().showConsoleView( 1103 AdtPlugin.getDefault().getAndroidConsole()); 1104 } 1105 1106 /** 1107 * Returns whether the {@link IAndroidTarget}s have been loaded from the SDK. 1108 */ 1109 public final LoadStatus getSdkLoadStatus() { 1110 synchronized (Sdk.getLock()) { 1111 return mSdkLoadedStatus; 1112 } 1113 } 1114 1115 /** 1116 * Sets the given {@link IJavaProject} to have its target resolved again once the SDK finishes 1117 * to load. 1118 */ 1119 public final void setProjectToResolve(IJavaProject javaProject) { 1120 synchronized (Sdk.getLock()) { 1121 mPostLoadProjectsToResolve.add(javaProject); 1122 } 1123 } 1124 1125 /** 1126 * Sets the given {@link IJavaProject} to have its target checked for consistency 1127 * once the SDK finishes to load. This is used if the target is resolved using cached 1128 * information while the SDK is loading. 1129 */ 1130 public final void setProjectToCheck(IJavaProject javaProject) { 1131 // only lock on 1132 synchronized (Sdk.getLock()) { 1133 mPostLoadProjectsToCheck.add(javaProject); 1134 } 1135 } 1136 1137 /** 1138 * Checks the location of the SDK in the prefs is valid. 1139 * If it is not, display a warning dialog to the user and try to display 1140 * some useful link to fix the situation (setup the preferences, perform an 1141 * update, etc.) 1142 * 1143 * @return True if the SDK location points to an SDK. 1144 * If false, the user has already been presented with a modal dialog explaining that. 1145 */ 1146 public boolean checkSdkLocationAndId() { 1147 String sdkLocation = AdtPrefs.getPrefs().getOsSdkFolder(); 1148 1149 return checkSdkLocationAndId(sdkLocation, new CheckSdkErrorHandler() { 1150 private String mTitle = "Android SDK"; 1151 1152 /** 1153 * Handle an error, which is the case where the check did not find any SDK. 1154 * This returns false to {@link AdtPlugin#checkSdkLocationAndId()}. 1155 */ 1156 @Override 1157 public boolean handleError(Solution solution, String message) { 1158 displayMessage(solution, message, MessageDialog.ERROR); 1159 return false; 1160 } 1161 1162 /** 1163 * Handle an warning, which is the case where the check found an SDK 1164 * but it might need to be repaired or is missing an expected component. 1165 * 1166 * This returns true to {@link AdtPlugin#checkSdkLocationAndId()}. 1167 */ 1168 @Override 1169 public boolean handleWarning(Solution solution, String message) { 1170 displayMessage(solution, message, MessageDialog.WARNING); 1171 return true; 1172 } 1173 1174 private void displayMessage( 1175 final Solution solution, 1176 final String message, 1177 final int dialogImageType) { 1178 final Display disp = getDisplay(); 1179 disp.asyncExec(new Runnable() { 1180 @Override 1181 public void run() { 1182 Shell shell = disp.getActiveShell(); 1183 if (shell == null) { 1184 shell = AdtPlugin.getShell(); 1185 } 1186 if (shell == null) { 1187 return; 1188 } 1189 1190 String customLabel = null; 1191 switch(solution) { 1192 case OPEN_ANDROID_PREFS: 1193 customLabel = "Open Preferences"; 1194 break; 1195 case OPEN_P2_UPDATE: 1196 customLabel = "Check for Updates"; 1197 break; 1198 case OPEN_SDK_MANAGER: 1199 customLabel = "Open SDK Manager"; 1200 break; 1201 } 1202 1203 String btnLabels[] = new String[customLabel == null ? 1 : 2]; 1204 btnLabels[0] = customLabel; 1205 btnLabels[btnLabels.length - 1] = IDialogConstants.CLOSE_LABEL; 1206 1207 MessageDialog dialog = new MessageDialog( 1208 shell, // parent 1209 mTitle, 1210 null, // dialogTitleImage 1211 message, 1212 dialogImageType, 1213 btnLabels, 1214 btnLabels.length - 1); 1215 int index = dialog.open(); 1216 1217 if (customLabel != null && index == 0) { 1218 switch(solution) { 1219 case OPEN_ANDROID_PREFS: 1220 openAndroidPrefs(); 1221 break; 1222 case OPEN_P2_UPDATE: 1223 openP2Update(); 1224 break; 1225 case OPEN_SDK_MANAGER: 1226 openSdkManager(); 1227 break; 1228 } 1229 } 1230 } 1231 }); 1232 } 1233 1234 private void openSdkManager() { 1235 // Open the standalone external SDK Manager since we know 1236 // that ADT on Windows is bound to be locking some SDK folders. 1237 // 1238 // Also when this is invoked because SdkManagerAction.run() fails, this 1239 // test will fail and we'll fallback on using the internal one. 1240 if (SdkManagerAction.openExternalSdkManager()) { 1241 return; 1242 } 1243 1244 // Otherwise open the regular SDK Manager bundled within ADT 1245 if (!SdkManagerAction.openAdtSdkManager()) { 1246 // We failed because the SDK location is undefined. In this case 1247 // let's open the preferences instead. 1248 openAndroidPrefs(); 1249 } 1250 } 1251 1252 private void openP2Update() { 1253 Display disp = getDisplay(); 1254 if (disp == null) { 1255 return; 1256 } 1257 disp.asyncExec(new Runnable() { 1258 @Override 1259 public void run() { 1260 String cmdId = "org.eclipse.equinox.p2.ui.sdk.update"; //$NON-NLS-1$ 1261 IWorkbench wb = PlatformUI.getWorkbench(); 1262 if (wb == null) { 1263 return; 1264 } 1265 1266 ICommandService cs = (ICommandService) wb.getService(ICommandService.class); 1267 IHandlerService is = (IHandlerService) wb.getService(IHandlerService.class); 1268 if (cs == null || is == null) { 1269 return; 1270 } 1271 1272 Command cmd = cs.getCommand(cmdId); 1273 if (cmd != null && cmd.isDefined()) { 1274 try { 1275 is.executeCommand(cmdId, null/*event*/); 1276 } catch (Exception ignore) { 1277 AdtPlugin.log(ignore, "Failed to execute command %s", cmdId); 1278 } 1279 } 1280 } 1281 }); 1282 } 1283 1284 private void openAndroidPrefs() { 1285 PreferenceDialog dialog = PreferencesUtil.createPreferenceDialogOn( 1286 getDisplay().getActiveShell(), 1287 "com.android.ide.eclipse.preferences.main", //$NON-NLS-1$ preferencePageId 1288 null, // displayedIds 1289 null); // data 1290 dialog.open(); 1291 } 1292 }); 1293 } 1294 1295 /** 1296 * Internal helper to perform the actual sdk location and id check. 1297 * <p/> 1298 * This is useful for callers who want to override what happens when the check 1299 * fails. Otherwise consider calling {@link #checkSdkLocationAndId()} that will 1300 * present a modal dialog to the user in case of failure. 1301 * 1302 * @param osSdkLocation The sdk directory, an OS path. Can be null. 1303 * @param errorHandler An checkSdkErrorHandler that can display a warning or an error. 1304 * @return False if there was an error or the result from the errorHandler invocation. 1305 */ 1306 public boolean checkSdkLocationAndId(@Nullable String osSdkLocation, 1307 @NonNull CheckSdkErrorHandler errorHandler) { 1308 if (osSdkLocation == null || osSdkLocation.trim().length() == 0) { 1309 return errorHandler.handleError( 1310 Solution.OPEN_ANDROID_PREFS, 1311 "Location of the Android SDK has not been setup in the preferences."); 1312 } 1313 1314 if (!osSdkLocation.endsWith(File.separator)) { 1315 osSdkLocation = osSdkLocation + File.separator; 1316 } 1317 1318 File osSdkFolder = new File(osSdkLocation); 1319 if (osSdkFolder.isDirectory() == false) { 1320 return errorHandler.handleError( 1321 Solution.OPEN_ANDROID_PREFS, 1322 String.format(Messages.Could_Not_Find_Folder, osSdkLocation)); 1323 } 1324 1325 String osTools = osSdkLocation + SdkConstants.OS_SDK_TOOLS_FOLDER; 1326 File toolsFolder = new File(osTools); 1327 if (toolsFolder.isDirectory() == false) { 1328 return errorHandler.handleError( 1329 Solution.OPEN_ANDROID_PREFS, 1330 String.format(Messages.Could_Not_Find_Folder_In_SDK, 1331 SdkConstants.FD_TOOLS, osSdkLocation)); 1332 } 1333 1334 // first check the min plug-in requirement as its error message is easier to figure 1335 // out for the user 1336 if (VersionCheck.checkVersion(osSdkLocation, errorHandler) == false) { 1337 return false; 1338 } 1339 1340 // check that we have both the tools component and the platform-tools component. 1341 String platformTools = osSdkLocation + SdkConstants.OS_SDK_PLATFORM_TOOLS_FOLDER; 1342 if (checkFolder(platformTools) == false) { 1343 return errorHandler.handleWarning( 1344 Solution.OPEN_SDK_MANAGER, 1345 "SDK Platform Tools component is missing!\n" + 1346 "Please use the SDK Manager to install it."); 1347 } 1348 1349 String tools = osSdkLocation + SdkConstants.OS_SDK_TOOLS_FOLDER; 1350 if (checkFolder(tools) == false) { 1351 return errorHandler.handleError( 1352 Solution.OPEN_SDK_MANAGER, 1353 "SDK Tools component is missing!\n" + 1354 "Please use the SDK Manager to install it."); 1355 } 1356 1357 // check the path to various tools we use to make sure nothing is missing. This is 1358 // not meant to be exhaustive. 1359 String[] filesToCheck = new String[] { 1360 osSdkLocation + getOsRelativeAdb(), 1361 osSdkLocation + getOsRelativeEmulator() 1362 }; 1363 for (String file : filesToCheck) { 1364 if (checkFile(file) == false) { 1365 return errorHandler.handleError( 1366 Solution.OPEN_ANDROID_PREFS, 1367 String.format(Messages.Could_Not_Find, file)); 1368 } 1369 } 1370 1371 return true; 1372 } 1373 1374 /** 1375 * Checks if a path reference a valid existing file. 1376 * @param osPath the os path to check. 1377 * @return true if the file exists and is, in fact, a file. 1378 */ 1379 private boolean checkFile(String osPath) { 1380 File file = new File(osPath); 1381 if (file.isFile() == false) { 1382 return false; 1383 } 1384 1385 return true; 1386 } 1387 1388 /** 1389 * Checks if a path reference a valid existing folder. 1390 * @param osPath the os path to check. 1391 * @return true if the folder exists and is, in fact, a folder. 1392 */ 1393 private boolean checkFolder(String osPath) { 1394 File file = new File(osPath); 1395 if (file.isDirectory() == false) { 1396 return false; 1397 } 1398 1399 return true; 1400 } 1401 1402 /** 1403 * Parses the SDK resources. 1404 */ 1405 private void parseSdkContent(long delay) { 1406 // Perform the update in a thread (here an Eclipse runtime job) 1407 // since this should never block the caller (especially the start method) 1408 Job job = new Job(Messages.AdtPlugin_Android_SDK_Content_Loader) { 1409 @SuppressWarnings("unchecked") 1410 @Override 1411 protected IStatus run(IProgressMonitor monitor) { 1412 try { 1413 1414 if (mParseSdkContentIsRunning) { 1415 return new Status(IStatus.WARNING, PLUGIN_ID, 1416 "An Android SDK is already being loaded. Please try again later."); 1417 } 1418 1419 mParseSdkContentIsRunning = true; 1420 1421 SubMonitor progress = SubMonitor.convert(monitor, 1422 "Initialize SDK Manager", 100); 1423 1424 Sdk sdk = Sdk.loadSdk(AdtPrefs.getPrefs().getOsSdkFolder()); 1425 1426 if (sdk != null) { 1427 ArrayList<IJavaProject> list = new ArrayList<IJavaProject>(); 1428 synchronized (Sdk.getLock()) { 1429 mSdkLoadedStatus = LoadStatus.LOADED; 1430 1431 progress.setTaskName("Check Projects"); 1432 1433 for (IJavaProject javaProject : mPostLoadProjectsToResolve) { 1434 IProject iProject = javaProject.getProject(); 1435 if (iProject.isOpen()) { 1436 // project that have been resolved before the sdk was loaded 1437 // will have a ProjectState where the IAndroidTarget is null 1438 // so we load the target now that the SDK is loaded. 1439 sdk.loadTargetAndBuildTools(Sdk.getProjectState(iProject)); 1440 list.add(javaProject); 1441 } 1442 } 1443 1444 // done with this list. 1445 mPostLoadProjectsToResolve.clear(); 1446 } 1447 1448 // check the projects that need checking. 1449 // The method modifies the list (it removes the project that 1450 // do not need to be resolved again). 1451 AndroidClasspathContainerInitializer.checkProjectsCache( 1452 mPostLoadProjectsToCheck); 1453 1454 list.addAll(mPostLoadProjectsToCheck); 1455 1456 // update the project that needs recompiling. 1457 if (list.size() > 0) { 1458 IJavaProject[] array = list.toArray( 1459 new IJavaProject[list.size()]); 1460 ProjectHelper.updateProjects(array); 1461 } 1462 1463 progress.worked(10); 1464 } else { 1465 // SDK failed to Load! 1466 // Sdk#loadSdk() has already displayed an error. 1467 synchronized (Sdk.getLock()) { 1468 mSdkLoadedStatus = LoadStatus.FAILED; 1469 } 1470 } 1471 1472 // Notify resource changed listeners 1473 progress.setTaskName("Refresh UI"); 1474 progress.setWorkRemaining(mTargetChangeListeners.size()); 1475 1476 // Clone the list before iterating, to avoid ConcurrentModification 1477 // exceptions 1478 final List<ITargetChangeListener> listeners = 1479 (List<ITargetChangeListener>)mTargetChangeListeners.clone(); 1480 final SubMonitor progress2 = progress; 1481 AdtPlugin.getDisplay().asyncExec(new Runnable() { 1482 @Override 1483 public void run() { 1484 for (ITargetChangeListener listener : listeners) { 1485 try { 1486 listener.onSdkLoaded(); 1487 } catch (Exception e) { 1488 AdtPlugin.log(e, "Failed to update a TargetChangeListener."); //$NON-NLS-1$ 1489 } finally { 1490 progress2.worked(1); 1491 } 1492 } 1493 } 1494 }); 1495 } catch (Throwable t) { 1496 log(t, "Unknown exception in parseSdkContent."); //$NON-NLS-1$ 1497 return new Status(IStatus.ERROR, PLUGIN_ID, 1498 "parseSdkContent failed", t); //$NON-NLS-1$ 1499 1500 } finally { 1501 mParseSdkContentIsRunning = false; 1502 if (monitor != null) { 1503 monitor.done(); 1504 } 1505 } 1506 1507 return Status.OK_STATUS; 1508 } 1509 }; 1510 job.setPriority(Job.BUILD); // build jobs are run after other interactive jobs 1511 job.setRule(ResourcesPlugin.getWorkspace().getRoot()); 1512 if (delay > 0) { 1513 job.schedule(delay); 1514 } else { 1515 job.schedule(); 1516 } 1517 } 1518 1519 /** Returns the global android console */ 1520 public MessageConsole getAndroidConsole() { 1521 return mAndroidConsole; 1522 } 1523 1524 // ----- Methods for Editors ------- 1525 1526 public void startEditors() { 1527 sAndroidLogoDesc = imageDescriptorFromPlugin(AdtPlugin.PLUGIN_ID, 1528 "/icons/android.png"); //$NON-NLS-1$ 1529 sAndroidLogo = sAndroidLogoDesc.createImage(); 1530 1531 // Add a resource listener to handle compiled resources. 1532 IWorkspace ws = ResourcesPlugin.getWorkspace(); 1533 mResourceMonitor = GlobalProjectMonitor.startMonitoring(ws); 1534 1535 if (mResourceMonitor != null) { 1536 try { 1537 setupEditors(mResourceMonitor); 1538 ResourceManager.setup(mResourceMonitor); 1539 LintDeltaProcessor.startListening(mResourceMonitor); 1540 } catch (Throwable t) { 1541 log(t, "ResourceManager.setup failed"); //$NON-NLS-1$ 1542 } 1543 } 1544 } 1545 1546 /** 1547 * The <code>AbstractUIPlugin</code> implementation of this <code>Plugin</code> 1548 * method saves this plug-in's preference and dialog stores and shuts down 1549 * its image registry (if they are in use). Subclasses may extend this 1550 * method, but must send super <b>last</b>. A try-finally statement should 1551 * be used where necessary to ensure that <code>super.shutdown()</code> is 1552 * always done. 1553 * 1554 * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) 1555 */ 1556 public void stopEditors() { 1557 sAndroidLogo.dispose(); 1558 1559 IconFactory.getInstance().dispose(); 1560 1561 LintDeltaProcessor.stopListening(mResourceMonitor); 1562 1563 // Remove the resource listener that handles compiled resources. 1564 IWorkspace ws = ResourcesPlugin.getWorkspace(); 1565 GlobalProjectMonitor.stopMonitoring(ws); 1566 1567 if (mRed != null) { 1568 mRed.dispose(); 1569 mRed = null; 1570 } 1571 } 1572 1573 /** 1574 * Returns an Image for the small Android logo. 1575 * 1576 * Callers should not dispose it. 1577 */ 1578 public static Image getAndroidLogo() { 1579 return sAndroidLogo; 1580 } 1581 1582 /** 1583 * Returns an {@link ImageDescriptor} for the small Android logo. 1584 * 1585 * Callers should not dispose it. 1586 */ 1587 public static ImageDescriptor getAndroidLogoDesc() { 1588 return sAndroidLogoDesc; 1589 } 1590 1591 /** 1592 * Returns the ResourceMonitor object. 1593 */ 1594 public GlobalProjectMonitor getResourceMonitor() { 1595 return mResourceMonitor; 1596 } 1597 1598 /** 1599 * Sets up the editor resource listener. 1600 * <p> 1601 * The listener handles: 1602 * <ul> 1603 * <li> Discovering newly created files, and ensuring that if they are in an Android 1604 * project, they default to the right XML editor. 1605 * <li> Discovering deleted files, and closing the corresponding editors if necessary. 1606 * This is only done for XML files, since other editors such as Java editors handles 1607 * it on their own. 1608 * <ul> 1609 * 1610 * This is called by the {@link AdtPlugin} during initialization. 1611 * 1612 * @param monitor The main Resource Monitor object. 1613 */ 1614 public void setupEditors(GlobalProjectMonitor monitor) { 1615 monitor.addFileListener(new IFileListener() { 1616 @Override 1617 public void fileChanged(@NonNull IFile file, @NonNull IMarkerDelta[] markerDeltas, 1618 int kind, @Nullable String extension, int flags, boolean isAndroidProject) { 1619 if (!isAndroidProject) { 1620 return; 1621 } 1622 if (flags == IResourceDelta.MARKERS || !SdkConstants.EXT_XML.equals(extension)) { 1623 // ONLY the markers changed, or not XML file: not relevant to this listener 1624 return; 1625 } 1626 1627 if (kind == IResourceDelta.REMOVED) { 1628 AdtUtils.closeEditors(file, false /*save*/); 1629 return; 1630 } 1631 1632 // The resources files must have a file path similar to 1633 // project/res/.../*.xml 1634 // There is no support for sub folders, so the segment count must be 4 1635 if (file.getFullPath().segmentCount() == 4) { 1636 // check if we are inside the res folder. 1637 String segment = file.getFullPath().segment(1); 1638 if (segment.equalsIgnoreCase(SdkConstants.FD_RESOURCES)) { 1639 // we are inside a res/ folder, get the ResourceFolderType of the 1640 // parent folder. 1641 String[] folderSegments = file.getParent().getName().split( 1642 SdkConstants.RES_QUALIFIER_SEP); 1643 1644 // get the enum for the resource type. 1645 ResourceFolderType type = ResourceFolderType.getTypeByName( 1646 folderSegments[0]); 1647 1648 if (type != null) { 1649 if (kind == IResourceDelta.ADDED) { 1650 // A new file {@code /res/type-config/some.xml} was added. 1651 // All the /res XML files are handled by the same common editor now. 1652 IDE.setDefaultEditor(file, CommonXmlEditor.ID); 1653 } 1654 } else { 1655 // if the res folder is null, this means the name is invalid, 1656 // in this case we remove whatever android editors that was set 1657 // as the default editor. 1658 IEditorDescriptor desc = IDE.getDefaultEditor(file); 1659 String editorId = desc.getId(); 1660 if (editorId.startsWith(AdtConstants.EDITORS_NAMESPACE)) { 1661 // reset the default editor. 1662 IDE.setDefaultEditor(file, null); 1663 } 1664 } 1665 } 1666 } 1667 } 1668 }, IResourceDelta.ADDED | IResourceDelta.REMOVED); 1669 1670 monitor.addProjectListener(new IProjectListener() { 1671 @Override 1672 public void projectClosed(IProject project) { 1673 // Close any editors referencing this project 1674 AdtUtils.closeEditors(project, true /*save*/); 1675 } 1676 1677 @Override 1678 public void projectDeleted(IProject project) { 1679 // Close any editors referencing this project 1680 AdtUtils.closeEditors(project, false /*save*/); 1681 } 1682 1683 @Override 1684 public void projectOpenedWithWorkspace(IProject project) { 1685 } 1686 1687 @Override 1688 public void allProjectsOpenedWithWorkspace() { 1689 } 1690 1691 @Override 1692 public void projectOpened(IProject project) { 1693 } 1694 1695 @Override 1696 public void projectRenamed(IProject project, IPath from) { 1697 } 1698 }); 1699 } 1700 1701 /** 1702 * Adds a new {@link ITargetChangeListener} to be notified when a new SDK is loaded, or when 1703 * a project has its target changed. 1704 */ 1705 public void addTargetListener(ITargetChangeListener listener) { 1706 mTargetChangeListeners.add(listener); 1707 } 1708 1709 /** 1710 * Removes an existing {@link ITargetChangeListener}. 1711 * @see #addTargetListener(ITargetChangeListener) 1712 */ 1713 public void removeTargetListener(ITargetChangeListener listener) { 1714 mTargetChangeListeners.remove(listener); 1715 } 1716 1717 /** 1718 * Updates all the {@link ITargetChangeListener}s that a target has changed for a given project. 1719 * <p/>Only editors related to that project should reload. 1720 */ 1721 @SuppressWarnings("unchecked") 1722 public void updateTargetListeners(final IProject project) { 1723 final List<ITargetChangeListener> listeners = 1724 (List<ITargetChangeListener>)mTargetChangeListeners.clone(); 1725 1726 AdtPlugin.getDisplay().asyncExec(new Runnable() { 1727 @Override 1728 public void run() { 1729 for (ITargetChangeListener listener : listeners) { 1730 try { 1731 listener.onProjectTargetChange(project); 1732 } catch (Exception e) { 1733 AdtPlugin.log(e, "Failed to update a TargetChangeListener."); //$NON-NLS-1$ 1734 } 1735 } 1736 } 1737 }); 1738 } 1739 1740 /** 1741 * Updates all the {@link ITargetChangeListener}s that a target data was loaded. 1742 * <p/>Only editors related to a project using this target should reload. 1743 */ 1744 @SuppressWarnings("unchecked") 1745 public void updateTargetListeners(final IAndroidTarget target) { 1746 final List<ITargetChangeListener> listeners = 1747 (List<ITargetChangeListener>)mTargetChangeListeners.clone(); 1748 1749 Display display = AdtPlugin.getDisplay(); 1750 if (display == null || display.isDisposed()) { 1751 return; 1752 } 1753 display.asyncExec(new Runnable() { 1754 @Override 1755 public void run() { 1756 for (ITargetChangeListener listener : listeners) { 1757 try { 1758 listener.onTargetLoaded(target); 1759 } catch (Exception e) { 1760 AdtPlugin.log(e, "Failed to update a TargetChangeListener."); //$NON-NLS-1$ 1761 } 1762 } 1763 } 1764 }); 1765 } 1766 1767 public static synchronized OutputStream getOutStream() { 1768 return sPlugin.mAndroidConsoleStream; 1769 } 1770 1771 public static synchronized OutputStream getErrorStream() { 1772 return sPlugin.mAndroidConsoleErrorStream; 1773 } 1774 1775 /** 1776 * Sets the named persistent property for the given file to the given value 1777 * 1778 * @param file the file to associate the property with 1779 * @param qname the name of the property 1780 * @param value the new value, or null to clear the property 1781 */ 1782 public static void setFileProperty(IFile file, QualifiedName qname, String value) { 1783 try { 1784 file.setPersistentProperty(qname, value); 1785 } catch (CoreException e) { 1786 log(e, "Cannot set property %1$s to %2$s", qname, value); 1787 } 1788 } 1789 1790 /** 1791 * Gets the named persistent file property from the given file 1792 * 1793 * @param file the file to look up properties for 1794 * @param qname the name of the property to look up 1795 * @return the property value, or null 1796 */ 1797 public static String getFileProperty(IFile file, QualifiedName qname) { 1798 try { 1799 return file.getPersistentProperty(qname); 1800 } catch (CoreException e) { 1801 log(e, "Cannot get property %1$s", qname); 1802 } 1803 1804 return null; 1805 } 1806 1807 /** 1808 * Conditionally reparses the content of the SDK if it has changed on-disk 1809 * and updates opened projects. 1810 * <p/> 1811 * The operation is asynchronous and happens in a background eclipse job. 1812 */ 1813 public void refreshSdk() { 1814 // SDK can't have changed if we haven't loaded it yet. 1815 final Sdk sdk = Sdk.getCurrent(); 1816 if (sdk == null) { 1817 return; 1818 } 1819 1820 Job job = new Job("Check Android SDK") { 1821 @Override 1822 protected IStatus run(IProgressMonitor monitor) { 1823 // SDK has changed if its location path is different. 1824 boolean changed = sdk.getSdkLocation() == null || 1825 !sdk.getSdkLocation().equals(AdtPrefs.getPrefs().getOsSdkFolder()); 1826 if (!changed) { 1827 // Check whether the target directories has potentially changed. 1828 changed = sdk.haveTargetsChanged(); 1829 } 1830 1831 if (changed) { 1832 monitor.setTaskName("Reload Android SDK"); 1833 reparseSdk(); 1834 } 1835 1836 monitor.done(); 1837 return Status.OK_STATUS; 1838 } 1839 }; 1840 job.setRule(ResourcesPlugin.getWorkspace().getRoot()); 1841 job.setPriority(Job.SHORT); // a short background job, not interactive. 1842 job.schedule(); 1843 } 1844 1845 /** 1846 * Reparses the content of the SDK and updates opened projects. 1847 * The operation is asynchronous and happens in a background eclipse job. 1848 * <p/> 1849 * This reloads the SDK all the time. To only perform this when it has potentially 1850 * changed, call {@link #refreshSdk()} instead. 1851 */ 1852 public void reparseSdk() { 1853 // add all the opened Android projects to the list of projects to be updated 1854 // after the SDK is reloaded 1855 synchronized (Sdk.getLock()) { 1856 // get the project to refresh. 1857 IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects(null /*filter*/); 1858 mPostLoadProjectsToResolve.addAll(Arrays.asList(androidProjects)); 1859 } 1860 1861 // parse the SDK resources at the new location 1862 parseSdkContent(0 /*immediately*/); 1863 } 1864 1865 /** 1866 * Prints messages, associated with a project to the specified stream 1867 * @param stream The stream to write to 1868 * @param tag The tag associated to the message. Can be null 1869 * @param objects The objects to print through their toString() method (or directly for 1870 * {@link String} objects. 1871 */ 1872 public static synchronized void printToStream(MessageConsoleStream stream, String tag, 1873 Object... objects) { 1874 String dateTag = AndroidPrintStream.getMessageTag(tag); 1875 1876 for (Object obj : objects) { 1877 stream.print(dateTag); 1878 stream.print(" "); //$NON-NLS-1$ 1879 if (obj instanceof String) { 1880 stream.println((String)obj); 1881 } else if (obj == null) { 1882 stream.println("(null)"); //$NON-NLS-1$ 1883 } else { 1884 stream.println(obj.toString()); 1885 } 1886 } 1887 } 1888 1889 // --------- ILogger methods ----------- 1890 1891 @Override 1892 public void error(@Nullable Throwable t, @Nullable String format, Object... args) { 1893 if (t != null) { 1894 log(t, format, args); 1895 } else { 1896 log(IStatus.ERROR, format, args); 1897 } 1898 } 1899 1900 @Override 1901 public void info(@NonNull String format, Object... args) { 1902 log(IStatus.INFO, format, args); 1903 } 1904 1905 @Override 1906 public void verbose(@NonNull String format, Object... args) { 1907 log(IStatus.INFO, format, args); 1908 } 1909 1910 @Override 1911 public void warning(@NonNull String format, Object... args) { 1912 log(IStatus.WARNING, format, args); 1913 } 1914 1915 /** 1916 * Opens the given URL in a browser tab 1917 * 1918 * @param url the URL to open in a browser 1919 */ 1920 public static void openUrl(URL url) { 1921 IWorkbenchBrowserSupport support = PlatformUI.getWorkbench().getBrowserSupport(); 1922 IWebBrowser browser; 1923 try { 1924 browser = support.createBrowser(PLUGIN_ID); 1925 browser.openURL(url); 1926 } catch (PartInitException e) { 1927 log(e, null); 1928 } 1929 } 1930 1931 /** 1932 * Opens a Java class for the given fully qualified class name 1933 * 1934 * @param project the project containing the class 1935 * @param fqcn the fully qualified class name of the class to be opened 1936 * @return true if the class was opened, false otherwise 1937 */ 1938 public static boolean openJavaClass(IProject project, String fqcn) { 1939 if (fqcn == null) { 1940 return false; 1941 } 1942 1943 // Handle inner classes 1944 if (fqcn.indexOf('$') != -1) { 1945 fqcn = fqcn.replaceAll("\\$", "."); //$NON-NLS-1$ //$NON-NLS-2$ 1946 } 1947 1948 try { 1949 if (project.hasNature(JavaCore.NATURE_ID)) { 1950 IJavaProject javaProject = JavaCore.create(project); 1951 IJavaElement result = javaProject.findType(fqcn); 1952 if (result != null) { 1953 return JavaUI.openInEditor(result) != null; 1954 } 1955 } 1956 } catch (Throwable e) { 1957 log(e, "Can't open class %1$s", fqcn); //$NON-NLS-1$ 1958 } 1959 1960 return false; 1961 } 1962 1963 /** 1964 * For a stack trace entry, specifying a class, method, and optionally 1965 * fileName and line number, open the corresponding line in the editor. 1966 * 1967 * @param fqcn the fully qualified name of the class 1968 * @param method the method name 1969 * @param fileName the file name, or null 1970 * @param lineNumber the line number or -1 1971 * @return true if the target location could be opened, false otherwise 1972 */ 1973 public static boolean openStackTraceLine(@Nullable String fqcn, 1974 @Nullable String method, @Nullable String fileName, int lineNumber) { 1975 return new SourceRevealer().revealMethod(fqcn + '.' + method, fileName, lineNumber, null); 1976 } 1977 1978 /** 1979 * Opens the given file and shows the given (optional) region in the editor (or 1980 * if no region is specified, opens the editor tab.) 1981 * 1982 * @param file the file to be opened 1983 * @param region an optional region which if set will be selected and shown to the 1984 * user 1985 * @throws PartInitException if something goes wrong 1986 */ 1987 public static void openFile(IFile file, IRegion region) throws PartInitException { 1988 openFile(file, region, true); 1989 } 1990 1991 // TODO: Make an openEditor which does the above, and make the above pass false for showEditor 1992 1993 /** 1994 * Opens the given file and shows the given (optional) region 1995 * 1996 * @param file the file to be opened 1997 * @param region an optional region which if set will be selected and shown to the 1998 * user 1999 * @param showEditorTab if true, front the editor tab after opening the file 2000 * @return the editor that was opened, or null if no editor was opened 2001 * @throws PartInitException if something goes wrong 2002 */ 2003 public static IEditorPart openFile(IFile file, IRegion region, boolean showEditorTab) 2004 throws PartInitException { 2005 IWorkbenchPage page = AdtUtils.getActiveWorkbenchPage(); 2006 if (page == null) { 2007 return null; 2008 } 2009 IEditorPart targetEditor = IDE.openEditor(page, file, true); 2010 if (targetEditor instanceof AndroidXmlEditor) { 2011 AndroidXmlEditor editor = (AndroidXmlEditor) targetEditor; 2012 if (region != null) { 2013 editor.show(region.getOffset(), region.getLength(), showEditorTab); 2014 } else if (showEditorTab) { 2015 editor.setActivePage(AndroidXmlEditor.TEXT_EDITOR_ID); 2016 } 2017 } else if (targetEditor instanceof AbstractTextEditor) { 2018 AbstractTextEditor editor = (AbstractTextEditor) targetEditor; 2019 if (region != null) { 2020 editor.setHighlightRange(region.getOffset(), region.getLength(), 2021 true /* moveCursor*/); 2022 } 2023 } 2024 2025 return targetEditor; 2026 } 2027 } 2028