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