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