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