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 com.android.ddmuilib.StackTracePanel; 20 import com.android.ddmuilib.StackTracePanel.ISourceRevealer; 21 import com.android.ddmuilib.console.DdmConsole; 22 import com.android.ddmuilib.console.IDdmConsole; 23 import com.android.ide.eclipse.adt.internal.VersionCheck; 24 import com.android.ide.eclipse.adt.internal.editors.IconFactory; 25 import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor; 26 import com.android.ide.eclipse.adt.internal.editors.menu.MenuEditor; 27 import com.android.ide.eclipse.adt.internal.editors.resources.ResourcesEditor; 28 import com.android.ide.eclipse.adt.internal.editors.xml.XmlEditor; 29 import com.android.ide.eclipse.adt.internal.launch.AndroidLaunchController; 30 import com.android.ide.eclipse.adt.internal.preferences.BuildPreferencePage; 31 import com.android.ide.eclipse.adt.internal.project.AndroidClasspathContainerInitializer; 32 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; 33 import com.android.ide.eclipse.adt.internal.project.ExportHelper; 34 import com.android.ide.eclipse.adt.internal.project.ProjectHelper; 35 import com.android.ide.eclipse.adt.internal.project.ExportHelper.IExportCallback; 36 import com.android.ide.eclipse.adt.internal.resources.manager.ProjectResources; 37 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolder; 38 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceFolderType; 39 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager; 40 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceMonitor; 41 import com.android.ide.eclipse.adt.internal.resources.manager.ResourceMonitor.IFileListener; 42 import com.android.ide.eclipse.adt.internal.sdk.AndroidTargetParser; 43 import com.android.ide.eclipse.adt.internal.sdk.LoadStatus; 44 import com.android.ide.eclipse.adt.internal.sdk.Sdk; 45 import com.android.ide.eclipse.adt.internal.sdk.Sdk.ITargetChangeListener; 46 import com.android.ide.eclipse.adt.internal.ui.EclipseUiHelper; 47 import com.android.ide.eclipse.adt.internal.wizards.export.ExportWizard; 48 import com.android.ide.eclipse.ddms.DdmsPlugin; 49 import com.android.ide.eclipse.ddms.ImageLoader; 50 import com.android.sdklib.IAndroidTarget; 51 import com.android.sdklib.SdkConstants; 52 import com.android.sdkstats.SdkStatsService; 53 54 import org.eclipse.core.resources.IFile; 55 import org.eclipse.core.resources.IFolder; 56 import org.eclipse.core.resources.IMarkerDelta; 57 import org.eclipse.core.resources.IProject; 58 import org.eclipse.core.resources.IResourceDelta; 59 import org.eclipse.core.resources.IWorkspace; 60 import org.eclipse.core.resources.ResourcesPlugin; 61 import org.eclipse.core.runtime.CoreException; 62 import org.eclipse.core.runtime.IProgressMonitor; 63 import org.eclipse.core.runtime.IStatus; 64 import org.eclipse.core.runtime.Preferences; 65 import org.eclipse.core.runtime.QualifiedName; 66 import org.eclipse.core.runtime.Status; 67 import org.eclipse.core.runtime.SubMonitor; 68 import org.eclipse.core.runtime.Preferences.IPropertyChangeListener; 69 import org.eclipse.core.runtime.Preferences.PropertyChangeEvent; 70 import org.eclipse.core.runtime.jobs.IJobChangeEvent; 71 import org.eclipse.core.runtime.jobs.Job; 72 import org.eclipse.core.runtime.jobs.JobChangeAdapter; 73 import org.eclipse.jdt.core.IJavaProject; 74 import org.eclipse.jface.dialogs.MessageDialog; 75 import org.eclipse.jface.preference.IPreferenceStore; 76 import org.eclipse.jface.resource.ImageDescriptor; 77 import org.eclipse.jface.viewers.StructuredSelection; 78 import org.eclipse.jface.wizard.WizardDialog; 79 import org.eclipse.swt.graphics.Color; 80 import org.eclipse.swt.graphics.Image; 81 import org.eclipse.swt.widgets.Display; 82 import org.eclipse.swt.widgets.Shell; 83 import org.eclipse.ui.IEditorDescriptor; 84 import org.eclipse.ui.IEditorPart; 85 import org.eclipse.ui.IWorkbench; 86 import org.eclipse.ui.IWorkbenchPage; 87 import org.eclipse.ui.PlatformUI; 88 import org.eclipse.ui.console.ConsolePlugin; 89 import org.eclipse.ui.console.IConsole; 90 import org.eclipse.ui.console.IConsoleConstants; 91 import org.eclipse.ui.console.MessageConsole; 92 import org.eclipse.ui.console.MessageConsoleStream; 93 import org.eclipse.ui.ide.IDE; 94 import org.eclipse.ui.part.FileEditorInput; 95 import org.eclipse.ui.plugin.AbstractUIPlugin; 96 import org.osgi.framework.Bundle; 97 import org.osgi.framework.BundleContext; 98 import org.osgi.framework.Constants; 99 import org.osgi.framework.Version; 100 101 import java.io.BufferedInputStream; 102 import java.io.BufferedReader; 103 import java.io.File; 104 import java.io.IOException; 105 import java.io.InputStreamReader; 106 import java.io.OutputStream; 107 import java.io.PrintStream; 108 import java.net.MalformedURLException; 109 import java.net.URL; 110 import java.util.ArrayList; 111 import java.util.Arrays; 112 import java.util.Calendar; 113 import java.util.List; 114 115 /** 116 * The activator class controls the plug-in life cycle 117 */ 118 public class AdtPlugin extends AbstractUIPlugin { 119 /** The plug-in ID */ 120 public static final String PLUGIN_ID = "com.android.ide.eclipse.adt"; //$NON-NLS-1$ 121 122 public final static String PREFS_SDK_DIR = PLUGIN_ID + ".sdk"; //$NON-NLS-1$ 123 124 public final static String PREFS_RES_AUTO_REFRESH = PLUGIN_ID + ".resAutoRefresh"; //$NON-NLS-1$ 125 126 public final static String PREFS_BUILD_VERBOSITY = PLUGIN_ID + ".buildVerbosity"; //$NON-NLS-1$ 127 128 public final static String PREFS_DEFAULT_DEBUG_KEYSTORE = PLUGIN_ID + ".defaultDebugKeyStore"; //$NON-NLS-1$ 129 130 public final static String PREFS_CUSTOM_DEBUG_KEYSTORE = PLUGIN_ID + ".customDebugKeyStore"; //$NON-NLS-1$ 131 132 public final static String PREFS_HOME_PACKAGE = PLUGIN_ID + ".homePackage"; //$NON-NLS-1$ 133 134 public final static String PREFS_EMU_OPTIONS = PLUGIN_ID + ".emuOptions"; //$NON-NLS-1$ 135 136 /** singleton instance */ 137 private static AdtPlugin sPlugin; 138 139 private static Image sAndroidLogo; 140 private static ImageDescriptor sAndroidLogoDesc; 141 142 /** default store, provided by eclipse */ 143 private IPreferenceStore mStore; 144 145 /** cached location for the sdk folder */ 146 private String mOsSdkLocation; 147 148 /** The global android console */ 149 private MessageConsole mAndroidConsole; 150 151 /** Stream to write in the android console */ 152 private MessageConsoleStream mAndroidConsoleStream; 153 154 /** Stream to write error messages to the android console */ 155 private MessageConsoleStream mAndroidConsoleErrorStream; 156 157 /** Image loader object */ 158 private ImageLoader mLoader; 159 160 /** Verbosity of the build */ 161 private int mBuildVerbosity = AdtConstants.BUILD_NORMAL; 162 163 /** Color used in the error console */ 164 private Color mRed; 165 166 /** Load status of the SDK. Any access MUST be in a synchronized(mPostLoadProjects) block */ 167 private LoadStatus mSdkIsLoaded = LoadStatus.LOADING; 168 /** Project to update once the SDK is loaded. 169 * Any access MUST be in a synchronized(mPostLoadProjectsToResolve) block */ 170 private final ArrayList<IJavaProject> mPostLoadProjectsToResolve = 171 new ArrayList<IJavaProject>(); 172 /** Project to check validity of cache vs actual once the SDK is loaded. 173 * Any access MUST be in a synchronized(mPostLoadProjectsToResolve) block */ 174 private final ArrayList<IJavaProject> mPostLoadProjectsToCheck = new ArrayList<IJavaProject>(); 175 176 private ResourceMonitor mResourceMonitor; 177 private ArrayList<ITargetChangeListener> mTargetChangeListeners = 178 new ArrayList<ITargetChangeListener>(); 179 180 protected boolean mSdkIsLoading; 181 182 /** 183 * Custom PrintStream for Dx output. This class overrides the method 184 * <code>println()</code> and adds the standard output tag with the 185 * date and the project name in front of every messages. 186 */ 187 private static final class AndroidPrintStream extends PrintStream { 188 private IProject mProject; 189 private String mPrefix; 190 191 /** 192 * Default constructor with project and output stream. 193 * The project is used to get the project name for the output tag. 194 * 195 * @param project The Project 196 * @param prefix A prefix to be printed before the actual message. Can be null 197 * @param stream The Stream 198 */ AndroidPrintStream(IProject project, String prefix, OutputStream stream)199 public AndroidPrintStream(IProject project, String prefix, OutputStream stream) { 200 super(stream); 201 mProject = project; 202 } 203 204 @Override println(String message)205 public void println(String message) { 206 // write the date/project tag first. 207 String tag = getMessageTag(mProject != null ? mProject.getName() : null); 208 209 print(tag); 210 if (mPrefix != null) { 211 print(mPrefix); 212 } 213 214 // then write the regular message 215 super.println(message); 216 } 217 } 218 219 /** 220 * An error handler for checkSdkLocationAndId() that will handle the generated error 221 * or warning message. Each method must return a boolean that will in turn be returned by 222 * checkSdkLocationAndId. 223 */ 224 public static abstract class CheckSdkErrorHandler { 225 /** Handle an error message during sdk location check. Returns whatever 226 * checkSdkLocationAndId() should returns. 227 */ handleError(String message)228 public abstract boolean handleError(String message); 229 230 /** Handle a warning message during sdk location check. Returns whatever 231 * checkSdkLocationAndId() should returns. 232 */ handleWarning(String message)233 public abstract boolean handleWarning(String message); 234 } 235 236 /** 237 * The constructor 238 */ AdtPlugin()239 public AdtPlugin() { 240 sPlugin = this; 241 } 242 243 /* 244 * (non-Javadoc) 245 * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext) 246 */ 247 @SuppressWarnings("deprecation") 248 @Override start(BundleContext context)249 public void start(BundleContext context) throws Exception { 250 super.start(context); 251 252 Display display = getDisplay(); 253 254 // set the default android console. 255 mAndroidConsole = new MessageConsole("Android", null); //$NON-NLS-1$ 256 ConsolePlugin.getDefault().getConsoleManager().addConsoles( 257 new IConsole[] { mAndroidConsole }); 258 259 // get the stream to write in the android console. 260 mAndroidConsoleStream = mAndroidConsole.newMessageStream(); 261 mAndroidConsoleErrorStream = mAndroidConsole.newMessageStream(); 262 mRed = new Color(display, 0xFF, 0x00, 0x00); 263 264 // because this can be run, in some cases, by a non ui thread, and beccause 265 // changing the console properties update the ui, we need to make this change 266 // in the ui thread. 267 display.asyncExec(new Runnable() { 268 public void run() { 269 mAndroidConsoleErrorStream.setColor(mRed); 270 } 271 }); 272 273 // set up the ddms console to use this objects 274 DdmConsole.setConsole(new IDdmConsole() { 275 public void printErrorToConsole(String message) { 276 AdtPlugin.printErrorToConsole((String)null, message); 277 } 278 public void printErrorToConsole(String[] messages) { 279 AdtPlugin.printErrorToConsole((String)null, (Object[])messages); 280 } 281 public void printToConsole(String message) { 282 AdtPlugin.printToConsole((String)null, message); 283 } 284 public void printToConsole(String[] messages) { 285 AdtPlugin.printToConsole((String)null, (Object[])messages); 286 } 287 }); 288 289 // get the eclipse store 290 mStore = getPreferenceStore(); 291 292 // set the listener for the preference change 293 Preferences prefs = getPluginPreferences(); 294 prefs.addPropertyChangeListener(new IPropertyChangeListener() { 295 public void propertyChange(PropertyChangeEvent event) { 296 // get the name of the property that changed. 297 String property = event.getProperty(); 298 299 // if the SDK changed, we update the cached version 300 if (PREFS_SDK_DIR.equals(property)) { 301 302 // get the new one from the preferences 303 mOsSdkLocation = (String)event.getNewValue(); 304 305 // make sure it ends with a separator 306 if (mOsSdkLocation.endsWith(File.separator) == false) { 307 mOsSdkLocation = mOsSdkLocation + File.separator; 308 } 309 310 // finally restart adb, in case it's a different version 311 DdmsPlugin.setAdb(getOsAbsoluteAdb(), true /* startAdb */); 312 313 // get the SDK location and build id. 314 if (checkSdkLocationAndId()) { 315 // if sdk if valid, reparse it 316 317 reparseSdk(); 318 } 319 } else if (PREFS_BUILD_VERBOSITY.equals(property)) { 320 mBuildVerbosity = BuildPreferencePage.getBuildLevel( 321 mStore.getString(PREFS_BUILD_VERBOSITY)); 322 } 323 } 324 }); 325 326 mOsSdkLocation = mStore.getString(PREFS_SDK_DIR); 327 328 // make sure it ends with a separator. Normally this is done when the preference 329 // is set. But to make sure older version still work, we fix it here as well. 330 if (mOsSdkLocation.length() > 0 && mOsSdkLocation.endsWith(File.separator) == false) { 331 mOsSdkLocation = mOsSdkLocation + File.separator; 332 } 333 334 // check the location of SDK 335 final boolean isSdkLocationValid = checkSdkLocationAndId(); 336 337 mBuildVerbosity = BuildPreferencePage.getBuildLevel( 338 mStore.getString(PREFS_BUILD_VERBOSITY)); 339 340 // create the loader that's able to load the images 341 mLoader = new ImageLoader(this); 342 343 // start the DdmsPlugin by setting the adb location, only if it is set already. 344 if (mOsSdkLocation.length() > 0) { 345 DdmsPlugin.setAdb(getOsAbsoluteAdb(), true); 346 } 347 348 // and give it the debug launcher for android projects 349 DdmsPlugin.setRunningAppDebugLauncher(new DdmsPlugin.IDebugLauncher() { 350 public boolean debug(String appName, int port) { 351 // search for an android project matching the process name 352 IProject project = ProjectHelper.findAndroidProjectByAppName(appName); 353 if (project != null) { 354 AndroidLaunchController.debugRunningApp(project, port); 355 return true; 356 } else { 357 return false; 358 } 359 } 360 }); 361 362 StackTracePanel.setSourceRevealer(new ISourceRevealer() { 363 public void reveal(String applicationName, String className, int line) { 364 IProject project = ProjectHelper.findAndroidProjectByAppName(applicationName); 365 if (project != null) { 366 BaseProjectHelper.revealSource(project, className, line); 367 } 368 } 369 }); 370 371 // setup export callback for editors 372 ExportHelper.setCallback(new IExportCallback() { 373 public void startExportWizard(IProject project) { 374 StructuredSelection selection = new StructuredSelection(project); 375 376 ExportWizard wizard = new ExportWizard(); 377 wizard.init(PlatformUI.getWorkbench(), selection); 378 WizardDialog dialog = new WizardDialog(getDisplay().getActiveShell(), 379 wizard); 380 dialog.open(); 381 } 382 }); 383 384 // initialize editors 385 startEditors(); 386 387 // Ping the usage server and parse the SDK content. 388 // This is deferred in separate jobs to avoid blocking the bundle start. 389 // We also serialize them to avoid too many parallel jobs when Eclipse starts. 390 Job pingJob = createPingUsageServerJob(); 391 pingJob.addJobChangeListener(new JobChangeAdapter() { 392 @Override 393 public void done(IJobChangeEvent event) { 394 super.done(event); 395 396 // Once the ping job is finished, start the SDK parser 397 if (isSdkLocationValid) { 398 // parse the SDK resources. 399 parseSdkContent(); 400 } 401 } 402 }); 403 // build jobs are run after other interactive jobs 404 pingJob.setPriority(Job.BUILD); 405 // Wait 2 seconds before starting the ping job. This leaves some time to the 406 // other bundles to initialize. 407 pingJob.schedule(2000 /*milliseconds*/); 408 } 409 410 /* 411 * (non-Javadoc) 412 * 413 * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) 414 */ 415 @Override stop(BundleContext context)416 public void stop(BundleContext context) throws Exception { 417 super.stop(context); 418 419 stopEditors(); 420 421 mRed.dispose(); 422 synchronized (AdtPlugin.class) { 423 sPlugin = null; 424 } 425 } 426 427 /** Return the image loader for the plugin */ getImageLoader()428 public static synchronized ImageLoader getImageLoader() { 429 if (sPlugin != null) { 430 return sPlugin.mLoader; 431 } 432 return null; 433 } 434 435 /** 436 * Returns the shared instance 437 * 438 * @return the shared instance 439 */ getDefault()440 public static synchronized AdtPlugin getDefault() { 441 return sPlugin; 442 } 443 getDisplay()444 public static Display getDisplay() { 445 IWorkbench bench = null; 446 synchronized (AdtPlugin.class) { 447 bench = sPlugin.getWorkbench(); 448 } 449 450 if (bench != null) { 451 return bench.getDisplay(); 452 } 453 return null; 454 } 455 456 /** Returns the adb path relative to the sdk folder */ getOsRelativeAdb()457 public static String getOsRelativeAdb() { 458 return SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_ADB; 459 } 460 461 /** Returns the zipalign path relative to the sdk folder */ getOsRelativeZipAlign()462 public static String getOsRelativeZipAlign() { 463 return SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_ZIPALIGN; 464 } 465 466 /** Returns the emulator path relative to the sdk folder */ getOsRelativeEmulator()467 public static String getOsRelativeEmulator() { 468 return SdkConstants.OS_SDK_TOOLS_FOLDER + SdkConstants.FN_EMULATOR; 469 } 470 471 /** Returns the absolute adb path */ getOsAbsoluteAdb()472 public static String getOsAbsoluteAdb() { 473 return getOsSdkFolder() + getOsRelativeAdb(); 474 } 475 476 /** Returns the absolute zipalign path */ getOsAbsoluteZipAlign()477 public static String getOsAbsoluteZipAlign() { 478 return getOsSdkFolder() + getOsRelativeZipAlign(); 479 } 480 481 /** Returns the absolute traceview path */ getOsAbsoluteTraceview()482 public static String getOsAbsoluteTraceview() { 483 return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER + 484 AndroidConstants.FN_TRACEVIEW; 485 } 486 487 /** Returns the absolute emulator path */ getOsAbsoluteEmulator()488 public static String getOsAbsoluteEmulator() { 489 return getOsSdkFolder() + getOsRelativeEmulator(); 490 } 491 492 /** 493 * Returns a Url file path to the javaDoc folder. 494 */ getUrlDoc()495 public static String getUrlDoc() { 496 return ProjectHelper.getJavaDocPath( 497 getOsSdkFolder() + AndroidConstants.WS_JAVADOC_FOLDER_LEAF); 498 } 499 500 /** 501 * Returns the SDK folder. 502 * Guaranteed to be terminated by a platform-specific path separator. 503 */ getOsSdkFolder()504 public static synchronized String getOsSdkFolder() { 505 if (sPlugin == null) { 506 return null; 507 } 508 509 if (sPlugin.mOsSdkLocation == null) { 510 sPlugin.mOsSdkLocation = sPlugin.mStore.getString(PREFS_SDK_DIR); 511 } 512 return sPlugin.mOsSdkLocation; 513 } 514 getOsSdkToolsFolder()515 public static String getOsSdkToolsFolder() { 516 return getOsSdkFolder() + SdkConstants.OS_SDK_TOOLS_FOLDER; 517 } 518 getAutoResRefresh()519 public static synchronized boolean getAutoResRefresh() { 520 if (sPlugin == null) { 521 return false; 522 } 523 return sPlugin.mStore.getBoolean(PREFS_RES_AUTO_REFRESH); 524 } 525 getBuildVerbosity()526 public static synchronized int getBuildVerbosity() { 527 if (sPlugin != null) { 528 return sPlugin.mBuildVerbosity; 529 } 530 531 return 0; 532 } 533 534 /** 535 * Returns an image descriptor for the image file at the given 536 * plug-in relative path 537 * 538 * @param path the path 539 * @return the image descriptor 540 */ getImageDescriptor(String path)541 public static ImageDescriptor getImageDescriptor(String path) { 542 return imageDescriptorFromPlugin(PLUGIN_ID, path); 543 } 544 545 /** 546 * Reads and returns the content of a text file embedded in the plugin jar 547 * file. 548 * @param filepath the file path to the text file 549 * @return null if the file could not be read 550 */ readEmbeddedTextFile(String filepath)551 public static String readEmbeddedTextFile(String filepath) { 552 Bundle bundle = null; 553 synchronized (AdtPlugin.class) { 554 if (sPlugin != null) { 555 bundle = sPlugin.getBundle(); 556 } else { 557 return null; 558 } 559 } 560 561 // attempt to get a file to one of the template. 562 try { 563 URL url = bundle.getEntry(AndroidConstants.WS_SEP + filepath); 564 if (url != null) { 565 BufferedReader reader = new BufferedReader( 566 new InputStreamReader(url.openStream())); 567 568 String line; 569 StringBuilder total = new StringBuilder(reader.readLine()); 570 while ((line = reader.readLine()) != null) { 571 total.append('\n'); 572 total.append(line); 573 } 574 575 return total.toString(); 576 } 577 } catch (MalformedURLException e) { 578 // we'll just return null. 579 } catch (IOException e) { 580 // we'll just return null. 581 } 582 583 return null; 584 } 585 586 /** 587 * Reads and returns the content of a binary file embedded in the plugin jar 588 * file. 589 * @param filepath the file path to the text file 590 * @return null if the file could not be read 591 */ readEmbeddedFile(String filepath)592 public static byte[] readEmbeddedFile(String filepath) { 593 Bundle bundle = null; 594 synchronized (AdtPlugin.class) { 595 if (sPlugin != null) { 596 bundle = sPlugin.getBundle(); 597 } else { 598 return null; 599 } 600 } 601 602 // attempt to get a file to one of the template. 603 try { 604 URL url = bundle.getEntry(AndroidConstants.WS_SEP + filepath); 605 if (url != null) { 606 // create a buffered reader to facilitate reading. 607 BufferedInputStream stream = new BufferedInputStream( 608 url.openStream()); 609 610 // get the size to read. 611 int avail = stream.available(); 612 613 // create the buffer and reads it. 614 byte[] buffer = new byte[avail]; 615 stream.read(buffer); 616 617 // and return. 618 return buffer; 619 } 620 } catch (MalformedURLException e) { 621 // we'll just return null. 622 } catch (IOException e) { 623 // we'll just return null;. 624 } 625 626 return null; 627 } 628 629 /** 630 * Displays an error dialog box. This dialog box is ran asynchronously in the ui thread, 631 * therefore this method can be called from any thread. 632 * @param title The title of the dialog box 633 * @param message The error message 634 */ displayError(final String title, final String message)635 public final static void displayError(final String title, final String message) { 636 // get the current Display 637 final Display display = getDisplay(); 638 639 // dialog box only run in ui thread.. 640 display.asyncExec(new Runnable() { 641 public void run() { 642 Shell shell = display.getActiveShell(); 643 MessageDialog.openError(shell, title, message); 644 } 645 }); 646 } 647 648 /** 649 * Displays a warning dialog box. This dialog box is ran asynchronously in the ui thread, 650 * therefore this method can be called from any thread. 651 * @param title The title of the dialog box 652 * @param message The warning message 653 */ displayWarning(final String title, final String message)654 public final static void displayWarning(final String title, final String message) { 655 // get the current Display 656 final Display display = getDisplay(); 657 658 // dialog box only run in ui thread.. 659 display.asyncExec(new Runnable() { 660 public void run() { 661 Shell shell = display.getActiveShell(); 662 MessageDialog.openWarning(shell, title, message); 663 } 664 }); 665 } 666 667 /** 668 * Display a yes/no question dialog box. This dialog is opened synchronously in the ui thread, 669 * therefore this message can be called from any thread. 670 * @param title The title of the dialog box 671 * @param message The error message 672 * @return true if OK was clicked. 673 */ displayPrompt(final String title, final String message)674 public final static boolean displayPrompt(final String title, final String message) { 675 // get the current Display and Shell 676 final Display display = getDisplay(); 677 678 // we need to ask the user what he wants to do. 679 final boolean[] result = new boolean[1]; 680 display.syncExec(new Runnable() { 681 public void run() { 682 Shell shell = display.getActiveShell(); 683 result[0] = MessageDialog.openQuestion(shell, title, message); 684 } 685 }); 686 return result[0]; 687 } 688 689 /** 690 * Logs a message to the default Eclipse log. 691 * 692 * @param severity The severity code. Valid values are: {@link IStatus#OK}, 693 * {@link IStatus#ERROR}, {@link IStatus#INFO}, {@link IStatus#WARNING} or 694 * {@link IStatus#CANCEL}. 695 * @param format The format string, like for {@link String#format(String, Object...)}. 696 * @param args The arguments for the format string, like for 697 * {@link String#format(String, Object...)}. 698 */ log(int severity, String format, Object ... args)699 public static void log(int severity, String format, Object ... args) { 700 String message = String.format(format, args); 701 Status status = new Status(severity, PLUGIN_ID, message); 702 getDefault().getLog().log(status); 703 } 704 705 /** 706 * Logs an exception to the default Eclipse log. 707 * <p/> 708 * The status severity is always set to ERROR. 709 * 710 * @param exception the exception to log. 711 * @param format The format string, like for {@link String#format(String, Object...)}. 712 * @param args The arguments for the format string, like for 713 * {@link String#format(String, Object...)}. 714 */ log(Throwable exception, String format, Object ... args)715 public static void log(Throwable exception, String format, Object ... args) { 716 String message = String.format(format, args); 717 Status status = new Status(IStatus.ERROR, PLUGIN_ID, message, exception); 718 getDefault().getLog().log(status); 719 } 720 721 /** 722 * This is a mix between log(Throwable) and printErrorToConsole. 723 * <p/> 724 * This logs the exception with an ERROR severity and the given printf-like format message. 725 * The same message is then printed on the Android error console with the associated tag. 726 * 727 * @param exception the exception to log. 728 * @param format The format string, like for {@link String#format(String, Object...)}. 729 * @param args The arguments for the format string, like for 730 * {@link String#format(String, Object...)}. 731 */ logAndPrintError(Throwable exception, String tag, String format, Object ... args)732 public static synchronized void logAndPrintError(Throwable exception, String tag, 733 String format, Object ... args) { 734 if (sPlugin != null) { 735 String message = String.format(format, args); 736 Status status = new Status(IStatus.ERROR, PLUGIN_ID, message, exception); 737 getDefault().getLog().log(status); 738 printToStream(sPlugin.mAndroidConsoleErrorStream, tag, message); 739 showAndroidConsole(); 740 } 741 } 742 743 /** 744 * Prints one or more error message to the android console. 745 * @param tag A tag to be associated with the message. Can be null. 746 * @param objects the objects to print through their <code>toString</code> method. 747 */ printErrorToConsole(String tag, Object... objects)748 public static synchronized void printErrorToConsole(String tag, Object... objects) { 749 if (sPlugin != null) { 750 printToStream(sPlugin.mAndroidConsoleErrorStream, tag, objects); 751 752 showAndroidConsole(); 753 } 754 } 755 756 /** 757 * Prints one or more error message to the android console. 758 * @param objects the objects to print through their <code>toString</code> method. 759 */ printErrorToConsole(Object... objects)760 public static void printErrorToConsole(Object... objects) { 761 printErrorToConsole((String)null, objects); 762 } 763 764 /** 765 * Prints one or more error message to the android console. 766 * @param project The project to which the message is associated. Can be null. 767 * @param objects the objects to print through their <code>toString</code> method. 768 */ printErrorToConsole(IProject project, Object... objects)769 public static void printErrorToConsole(IProject project, Object... objects) { 770 String tag = project != null ? project.getName() : null; 771 printErrorToConsole(tag, objects); 772 } 773 774 /** 775 * Prints one or more build messages to the android console, filtered by Build output verbosity. 776 * @param level Verbosity level of the message. 777 * @param project The project to which the message is associated. Can be null. 778 * @param objects the objects to print through their <code>toString</code> method. 779 * @see AdtConstants#BUILD_ALWAYS 780 * @see AdtConstants#BUILD_NORMAL 781 * @see AdtConstants#BUILD_VERBOSE 782 */ printBuildToConsole(int level, IProject project, Object... objects)783 public static synchronized void printBuildToConsole(int level, IProject project, 784 Object... objects) { 785 if (sPlugin != null) { 786 if (level <= sPlugin.mBuildVerbosity) { 787 String tag = project != null ? project.getName() : null; 788 printToStream(sPlugin.mAndroidConsoleStream, tag, objects); 789 } 790 } 791 } 792 793 /** 794 * Prints one or more message to the android console. 795 * @param tag The tag to be associated with the message. Can be null. 796 * @param objects the objects to print through their <code>toString</code> method. 797 */ printToConsole(String tag, Object... objects)798 public static synchronized void printToConsole(String tag, Object... objects) { 799 if (sPlugin != null) { 800 printToStream(sPlugin.mAndroidConsoleStream, tag, objects); 801 } 802 } 803 804 /** 805 * Prints one or more message to the android console. 806 * @param project The project to which the message is associated. Can be null. 807 * @param objects the objects to print through their <code>toString</code> method. 808 */ printToConsole(IProject project, Object... objects)809 public static void printToConsole(IProject project, Object... objects) { 810 String tag = project != null ? project.getName() : null; 811 printToConsole(tag, objects); 812 } 813 814 /** Force the display of the android console */ showAndroidConsole()815 public static void showAndroidConsole() { 816 // first make sure the console is in the workbench 817 EclipseUiHelper.showView(IConsoleConstants.ID_CONSOLE_VIEW, true); 818 819 // now make sure it's not docked. 820 ConsolePlugin.getDefault().getConsoleManager().showConsoleView( 821 AdtPlugin.getDefault().getAndroidConsole()); 822 } 823 824 /** 825 * Returns an standard PrintStream object for a specific project.<br> 826 * This PrintStream will add a date/project at the beginning of every 827 * <code>println()</code> output. 828 * 829 * @param project The project object 830 * @param prefix The prefix to be added to the message. Can be null. 831 * @return a new PrintStream 832 */ getOutPrintStream(IProject project, String prefix)833 public static synchronized PrintStream getOutPrintStream(IProject project, String prefix) { 834 if (sPlugin != null) { 835 return new AndroidPrintStream(project, prefix, sPlugin.mAndroidConsoleStream); 836 } 837 838 return null; 839 } 840 841 /** 842 * Returns an error PrintStream object for a specific project.<br> 843 * This PrintStream will add a date/project at the beginning of every 844 * <code>println()</code> output. 845 * 846 * @param project The project object 847 * @param prefix The prefix to be added to the message. Can be null. 848 * @return a new PrintStream 849 */ getErrPrintStream(IProject project, String prefix)850 public static synchronized PrintStream getErrPrintStream(IProject project, String prefix) { 851 if (sPlugin != null) { 852 return new AndroidPrintStream(project, prefix, sPlugin.mAndroidConsoleErrorStream); 853 } 854 855 return null; 856 } 857 858 /** 859 * Returns whether the Sdk has been loaded. 860 */ getSdkLoadStatus()861 public final LoadStatus getSdkLoadStatus() { 862 synchronized (getSdkLockObject()) { 863 return mSdkIsLoaded; 864 } 865 } 866 867 /** 868 * Returns the lock object for SDK loading. If you wish to do things while the SDK is loading, 869 * you must synchronize on this object. 870 */ getSdkLockObject()871 public final Object getSdkLockObject() { 872 return mPostLoadProjectsToResolve; 873 } 874 875 /** 876 * Sets the given {@link IJavaProject} to have its target resolved again once the SDK finishes 877 * to load. 878 */ setProjectToResolve(IJavaProject javaProject)879 public final void setProjectToResolve(IJavaProject javaProject) { 880 synchronized (getSdkLockObject()) { 881 mPostLoadProjectsToResolve.add(javaProject); 882 } 883 } 884 885 /** 886 * Sets the given {@link IJavaProject} to have its target checked for consistency 887 * once the SDK finishes to load. This is used if the target is resolved using cached 888 * information while the SDK is loading. 889 */ setProjectToCheck(IJavaProject javaProject)890 public final void setProjectToCheck(IJavaProject javaProject) { 891 // only lock on 892 synchronized (getSdkLockObject()) { 893 mPostLoadProjectsToCheck.add(javaProject); 894 } 895 } 896 897 /** 898 * Checks the location of the SDK is valid and if it is, grab the SDK API version 899 * from the SDK. 900 * @return false if the location is not correct. 901 */ checkSdkLocationAndId()902 private boolean checkSdkLocationAndId() { 903 if (mOsSdkLocation == null || mOsSdkLocation.length() == 0) { 904 displayError(Messages.Dialog_Title_SDK_Location, Messages.SDK_Not_Setup); 905 return false; 906 } 907 908 return checkSdkLocationAndId(mOsSdkLocation, new CheckSdkErrorHandler() { 909 @Override 910 public boolean handleError(String message) { 911 AdtPlugin.displayError(Messages.Dialog_Title_SDK_Location, 912 String.format(Messages.Error_Check_Prefs, message)); 913 return false; 914 } 915 916 @Override 917 public boolean handleWarning(String message) { 918 AdtPlugin.displayWarning(Messages.Dialog_Title_SDK_Location, message); 919 return true; 920 } 921 }); 922 } 923 924 /** 925 * Internal helper to perform the actual sdk location and id check. 926 * 927 * @param osSdkLocation The sdk directory, an OS path. 928 * @param errorHandler An checkSdkErrorHandler that can display a warning or an error. 929 * @return False if there was an error or the result from the errorHandler invocation. 930 */ 931 public boolean checkSdkLocationAndId(String osSdkLocation, CheckSdkErrorHandler errorHandler) { 932 if (osSdkLocation.endsWith(File.separator) == false) { 933 osSdkLocation = osSdkLocation + File.separator; 934 } 935 936 File osSdkFolder = new File(osSdkLocation); 937 if (osSdkFolder.isDirectory() == false) { 938 return errorHandler.handleError( 939 String.format(Messages.Could_Not_Find_Folder, osSdkLocation)); 940 } 941 942 String osTools = osSdkLocation + SdkConstants.OS_SDK_TOOLS_FOLDER; 943 File toolsFolder = new File(osTools); 944 if (toolsFolder.isDirectory() == false) { 945 return errorHandler.handleError( 946 String.format(Messages.Could_Not_Find_Folder_In_SDK, 947 SdkConstants.FD_TOOLS, osSdkLocation)); 948 } 949 950 // check the path to various tools we use 951 String[] filesToCheck = new String[] { 952 osSdkLocation + getOsRelativeAdb(), 953 osSdkLocation + getOsRelativeEmulator() 954 }; 955 for (String file : filesToCheck) { 956 if (checkFile(file) == false) { 957 return errorHandler.handleError(String.format(Messages.Could_Not_Find, file)); 958 } 959 } 960 961 // check the SDK build id/version and the plugin version. 962 return VersionCheck.checkVersion(osSdkLocation, errorHandler); 963 } 964 965 /** 966 * Checks if a path reference a valid existing file. 967 * @param osPath the os path to check. 968 * @return true if the file exists and is, in fact, a file. 969 */ 970 private boolean checkFile(String osPath) { 971 File file = new File(osPath); 972 if (file.isFile() == false) { 973 return false; 974 } 975 976 return true; 977 } 978 979 /** 980 * Creates a job than can ping the usage server. 981 */ 982 private Job createPingUsageServerJob() { 983 // In order to not block the plugin loading, so we spawn another thread. 984 Job job = new Job("Android SDK Ping") { // Job name, visible in progress view 985 @Override 986 protected IStatus run(IProgressMonitor monitor) { 987 try { 988 pingUsageServer(); //$NON-NLS-1$ 989 990 return Status.OK_STATUS; 991 } catch (Throwable t) { 992 log(t, "pingUsageServer failed"); //$NON-NLS-1$ 993 return new Status(IStatus.ERROR, PLUGIN_ID, 994 "pingUsageServer failed", t); //$NON-NLS-1$ 995 } 996 } 997 }; 998 return job; 999 } 1000 1001 /** 1002 * Parses the SDK resources. 1003 */ 1004 private void parseSdkContent() { 1005 // Perform the update in a thread (here an Eclipse runtime job) 1006 // since this should never block the caller (especially the start method) 1007 Job job = new Job(Messages.AdtPlugin_Android_SDK_Content_Loader) { 1008 @SuppressWarnings("unchecked") 1009 @Override 1010 protected IStatus run(IProgressMonitor monitor) { 1011 try { 1012 1013 if (mSdkIsLoading) { 1014 return new Status(IStatus.WARNING, PLUGIN_ID, 1015 "An Android SDK is already being loaded. Please try again later."); 1016 } 1017 1018 mSdkIsLoading = true; 1019 1020 SubMonitor progress = SubMonitor.convert(monitor, 1021 "Initialize SDK Manager", 100); 1022 1023 Sdk sdk = Sdk.loadSdk(mOsSdkLocation); 1024 1025 if (sdk != null) { 1026 1027 progress.setTaskName(Messages.AdtPlugin_Parsing_Resources); 1028 1029 final IAndroidTarget[] targets = sdk.getTargets(); 1030 final int n = targets.length; 1031 if (n > 0) { 1032 // load the rest of the targets. 1033 // TODO: make this on-demand. 1034 int w = 60 / n; 1035 for (IAndroidTarget target : targets) { 1036 SubMonitor p2 = progress.newChild(w); 1037 IStatus status = new AndroidTargetParser(target).run(p2); 1038 if (status.getCode() != IStatus.OK) { 1039 synchronized (getSdkLockObject()) { 1040 mSdkIsLoaded = LoadStatus.FAILED; 1041 mPostLoadProjectsToResolve.clear(); 1042 } 1043 return status; 1044 } 1045 } 1046 } 1047 1048 ArrayList<IJavaProject> list = new ArrayList<IJavaProject>(); 1049 synchronized (getSdkLockObject()) { 1050 mSdkIsLoaded = LoadStatus.LOADED; 1051 1052 progress.setTaskName("Check Projects"); 1053 1054 for (IJavaProject javaProject : mPostLoadProjectsToResolve) { 1055 if (javaProject.getProject().isOpen()) { 1056 list.add(javaProject); 1057 } 1058 } 1059 1060 // done with this list. 1061 mPostLoadProjectsToResolve.clear(); 1062 } 1063 1064 // check the projects that need checking. 1065 // The method modifies the list (it removes the project that 1066 // do not need to be resolved again). 1067 AndroidClasspathContainerInitializer.checkProjectsCache( 1068 mPostLoadProjectsToCheck); 1069 1070 list.addAll(mPostLoadProjectsToCheck); 1071 1072 // update the project that needs recompiling. 1073 if (list.size() > 0) { 1074 IJavaProject[] array = list.toArray( 1075 new IJavaProject[list.size()]); 1076 AndroidClasspathContainerInitializer.updateProjects(array); 1077 } 1078 1079 progress.worked(10); 1080 } 1081 1082 // Notify resource changed listeners 1083 progress.setTaskName("Refresh UI"); 1084 progress.setWorkRemaining(mTargetChangeListeners.size()); 1085 1086 // Clone the list before iterating, to avoid Concurrent Modification 1087 // exceptions 1088 final List<ITargetChangeListener> listeners = 1089 (List<ITargetChangeListener>)mTargetChangeListeners.clone(); 1090 final SubMonitor progress2 = progress; 1091 AdtPlugin.getDisplay().syncExec(new Runnable() { 1092 public void run() { 1093 for (ITargetChangeListener listener : listeners) { 1094 try { 1095 listener.onTargetsLoaded(); 1096 } catch (Exception e) { 1097 AdtPlugin.log(e, "Failed to update a TargetChangeListener."); //$NON-NLS-1$ 1098 } finally { 1099 progress2.worked(1); 1100 } 1101 } 1102 } 1103 }); 1104 } catch (Throwable t) { 1105 log(t, "Unknown exception in parseSdkContent."); //$NON-NLS-1$ 1106 return new Status(IStatus.ERROR, PLUGIN_ID, 1107 "parseSdkContent failed", t); //$NON-NLS-1$ 1108 1109 } finally { 1110 mSdkIsLoading = false; 1111 if (monitor != null) { 1112 monitor.done(); 1113 } 1114 } 1115 1116 return Status.OK_STATUS; 1117 } 1118 }; 1119 job.setPriority(Job.BUILD); // build jobs are run after other interactive jobs 1120 job.schedule(); 1121 } 1122 1123 /** Returns the global android console */ 1124 public MessageConsole getAndroidConsole() { 1125 return mAndroidConsole; 1126 } 1127 1128 // ----- Methods for Editors ------- 1129 1130 public void startEditors() { 1131 sAndroidLogoDesc = imageDescriptorFromPlugin(AdtPlugin.PLUGIN_ID, 1132 "/icons/android.png"); //$NON-NLS-1$ 1133 sAndroidLogo = sAndroidLogoDesc.createImage(); 1134 1135 // Add a resource listener to handle compiled resources. 1136 IWorkspace ws = ResourcesPlugin.getWorkspace(); 1137 mResourceMonitor = ResourceMonitor.startMonitoring(ws); 1138 1139 if (mResourceMonitor != null) { 1140 try { 1141 setupDefaultEditor(mResourceMonitor); 1142 ResourceManager.setup(mResourceMonitor); 1143 } catch (Throwable t) { 1144 log(t, "ResourceManager.setup failed"); //$NON-NLS-1$ 1145 } 1146 } 1147 } 1148 1149 /** 1150 * The <code>AbstractUIPlugin</code> implementation of this <code>Plugin</code> 1151 * method saves this plug-in's preference and dialog stores and shuts down 1152 * its image registry (if they are in use). Subclasses may extend this 1153 * method, but must send super <b>last</b>. A try-finally statement should 1154 * be used where necessary to ensure that <code>super.shutdown()</code> is 1155 * always done. 1156 * 1157 * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) 1158 */ 1159 public void stopEditors() { 1160 sAndroidLogo.dispose(); 1161 1162 IconFactory.getInstance().Dispose(); 1163 1164 // Remove the resource listener that handles compiled resources. 1165 IWorkspace ws = ResourcesPlugin.getWorkspace(); 1166 ResourceMonitor.stopMonitoring(ws); 1167 1168 mRed.dispose(); 1169 } 1170 1171 /** 1172 * Returns an Image for the small Android logo. 1173 * 1174 * Callers should not dispose it. 1175 */ 1176 public static Image getAndroidLogo() { 1177 return sAndroidLogo; 1178 } 1179 1180 /** 1181 * Returns an {@link ImageDescriptor} for the small Android logo. 1182 * 1183 * Callers should not dispose it. 1184 */ 1185 public static ImageDescriptor getAndroidLogoDesc() { 1186 return sAndroidLogoDesc; 1187 } 1188 1189 /** 1190 * Returns the ResourceMonitor object. 1191 */ 1192 public ResourceMonitor getResourceMonitor() { 1193 return mResourceMonitor; 1194 } 1195 1196 /** 1197 * Sets up the editor to register default editors for resource files when needed. 1198 * 1199 * This is called by the {@link AdtPlugin} during initialization. 1200 * 1201 * @param monitor The main Resource Monitor object. 1202 */ 1203 public void setupDefaultEditor(ResourceMonitor monitor) { 1204 monitor.addFileListener(new IFileListener() { 1205 1206 private static final String UNKNOWN_EDITOR = "unknown-editor"; //$NON-NLS-1$ 1207 1208 /* (non-Javadoc) 1209 * Sent when a file changed. 1210 * @param file The file that changed. 1211 * @param markerDeltas The marker deltas for the file. 1212 * @param kind The change kind. This is equivalent to 1213 * {@link IResourceDelta#accept(IResourceDeltaVisitor)} 1214 * 1215 * @see IFileListener#fileChanged 1216 */ 1217 public void fileChanged(IFile file, IMarkerDelta[] markerDeltas, int kind) { 1218 if (AndroidConstants.EXT_XML.equals(file.getFileExtension())) { 1219 // The resources files must have a file path similar to 1220 // project/res/.../*.xml 1221 // There is no support for sub folders, so the segment count must be 4 1222 if (file.getFullPath().segmentCount() == 4) { 1223 // check if we are inside the res folder. 1224 String segment = file.getFullPath().segment(1); 1225 if (segment.equalsIgnoreCase(SdkConstants.FD_RESOURCES)) { 1226 // we are inside a res/ folder, get the actual ResourceFolder 1227 ProjectResources resources = ResourceManager.getInstance(). 1228 getProjectResources(file.getProject()); 1229 1230 // This happens when importing old Android projects in Eclipse 1231 // that lack the container (probably because resources fail to build 1232 // properly.) 1233 if (resources == null) { 1234 log(IStatus.INFO, 1235 "getProjectResources failed for path %1$s in project %2$s", //$NON-NLS-1$ 1236 file.getFullPath().toOSString(), 1237 file.getProject().getName()); 1238 return; 1239 } 1240 1241 ResourceFolder resFolder = resources.getResourceFolder( 1242 (IFolder)file.getParent()); 1243 1244 if (resFolder != null) { 1245 if (kind == IResourceDelta.ADDED) { 1246 resourceAdded(file, resFolder.getType()); 1247 } else if (kind == IResourceDelta.CHANGED) { 1248 resourceChanged(file, resFolder.getType()); 1249 } 1250 } else { 1251 // if the res folder is null, this means the name is invalid, 1252 // in this case we remove whatever android editors that was set 1253 // as the default editor. 1254 IEditorDescriptor desc = IDE.getDefaultEditor(file); 1255 String editorId = desc.getId(); 1256 if (editorId.startsWith(AndroidConstants.EDITORS_NAMESPACE)) { 1257 // reset the default editor. 1258 IDE.setDefaultEditor(file, null); 1259 } 1260 } 1261 } 1262 } 1263 } 1264 } 1265 1266 private void resourceAdded(IFile file, ResourceFolderType type) { 1267 // set the default editor based on the type. 1268 if (type == ResourceFolderType.LAYOUT) { 1269 IDE.setDefaultEditor(file, LayoutEditor.ID); 1270 } else if (type == ResourceFolderType.DRAWABLE 1271 || type == ResourceFolderType.VALUES) { 1272 IDE.setDefaultEditor(file, ResourcesEditor.ID); 1273 } else if (type == ResourceFolderType.MENU) { 1274 IDE.setDefaultEditor(file, MenuEditor.ID); 1275 } else if (type == ResourceFolderType.XML) { 1276 if (XmlEditor.canHandleFile(file)) { 1277 IDE.setDefaultEditor(file, XmlEditor.ID); 1278 } else { 1279 // set a property to determine later if the XML can be handled 1280 QualifiedName qname = new QualifiedName( 1281 AdtPlugin.PLUGIN_ID, 1282 UNKNOWN_EDITOR); 1283 try { 1284 file.setPersistentProperty(qname, "1"); //$NON-NLS-1$ 1285 } catch (CoreException e) { 1286 // pass 1287 } 1288 } 1289 } 1290 } 1291 1292 private void resourceChanged(IFile file, ResourceFolderType type) { 1293 if (type == ResourceFolderType.XML) { 1294 IEditorDescriptor ed = IDE.getDefaultEditor(file); 1295 if (ed == null || ed.getId() != XmlEditor.ID) { 1296 QualifiedName qname = new QualifiedName( 1297 AdtPlugin.PLUGIN_ID, 1298 UNKNOWN_EDITOR); 1299 String prop = null; 1300 try { 1301 prop = file.getPersistentProperty(qname); 1302 } catch (CoreException e) { 1303 // pass 1304 } 1305 if (prop != null && XmlEditor.canHandleFile(file)) { 1306 try { 1307 // remove the property & set editor 1308 file.setPersistentProperty(qname, null); 1309 IWorkbenchPage page = PlatformUI.getWorkbench(). 1310 getActiveWorkbenchWindow().getActivePage(); 1311 1312 IEditorPart oldEditor = page.findEditor(new FileEditorInput(file)); 1313 if (oldEditor != null && 1314 AdtPlugin.displayPrompt("Android XML Editor", 1315 String.format("The file you just saved as been recognized as a file that could be better handled using the Android XML Editor. Do you want to edit '%1$s' using the Android XML editor instead?", 1316 file.getFullPath()))) { 1317 IDE.setDefaultEditor(file, XmlEditor.ID); 1318 IEditorPart newEditor = page.openEditor( 1319 new FileEditorInput(file), 1320 XmlEditor.ID, 1321 true, /* activate */ 1322 IWorkbenchPage.MATCH_NONE); 1323 1324 if (newEditor != null) { 1325 page.closeEditor(oldEditor, true /* save */); 1326 } 1327 } 1328 } catch (CoreException e) { 1329 // setPersistentProperty or page.openEditor may have failed 1330 } 1331 } 1332 } 1333 } 1334 } 1335 1336 }, IResourceDelta.ADDED | IResourceDelta.CHANGED); 1337 } 1338 1339 /** 1340 * Adds a new {@link ITargetChangeListener} to be notified when a new SDK is loaded, or when 1341 * a project has its target changed. 1342 */ 1343 public void addTargetListener(ITargetChangeListener listener) { 1344 mTargetChangeListeners.add(listener); 1345 } 1346 1347 /** 1348 * Removes an existing {@link ITargetChangeListener}. 1349 * @see #addTargetListener(ITargetChangeListener) 1350 */ 1351 public void removeTargetListener(ITargetChangeListener listener) { 1352 mTargetChangeListeners.remove(listener); 1353 } 1354 1355 /** 1356 * Updates all the {@link ITargetChangeListener} that a target has changed for a given project. 1357 * <p/>Only editors related to that project should reload. 1358 */ 1359 @SuppressWarnings("unchecked") 1360 public void updateTargetListener(final IProject project) { 1361 final List<ITargetChangeListener> listeners = 1362 (List<ITargetChangeListener>)mTargetChangeListeners.clone(); 1363 1364 AdtPlugin.getDisplay().asyncExec(new Runnable() { 1365 public void run() { 1366 for (ITargetChangeListener listener : listeners) { 1367 try { 1368 listener.onProjectTargetChange(project); 1369 } catch (Exception e) { 1370 AdtPlugin.log(e, "Failed to update a TargetChangeListener."); //$NON-NLS-1$ 1371 } 1372 } 1373 } 1374 }); 1375 } 1376 1377 public static synchronized OutputStream getErrorStream() { 1378 return sPlugin.mAndroidConsoleErrorStream; 1379 } 1380 1381 /** 1382 * Pings the usage start server. 1383 */ 1384 private void pingUsageServer() { 1385 // get the version of the plugin 1386 String versionString = (String) getBundle().getHeaders().get( 1387 Constants.BUNDLE_VERSION); 1388 Version version = new Version(versionString); 1389 1390 versionString = String.format("%1$d.%2$d.%3$d", version.getMajor(), //$NON-NLS-1$ 1391 version.getMinor(), version.getMicro()); 1392 1393 SdkStatsService.ping("adt", versionString, getDisplay()); //$NON-NLS-1$ 1394 } 1395 1396 /** 1397 * Reparses the content of the SDK and updates opened projects. 1398 */ 1399 public void reparseSdk() { 1400 // add all the opened Android projects to the list of projects to be updated 1401 // after the SDK is reloaded 1402 synchronized (getSdkLockObject()) { 1403 // get the project to refresh. 1404 IJavaProject[] androidProjects = BaseProjectHelper.getAndroidProjects(); 1405 mPostLoadProjectsToResolve.addAll(Arrays.asList(androidProjects)); 1406 } 1407 1408 // parse the SDK resources at the new location 1409 parseSdkContent(); 1410 } 1411 1412 /** 1413 * Prints messages, associated with a project to the specified stream 1414 * @param stream The stream to write to 1415 * @param tag The tag associated to the message. Can be null 1416 * @param objects The objects to print through their toString() method (or directly for 1417 * {@link String} objects. 1418 */ 1419 public static synchronized void printToStream(MessageConsoleStream stream, String tag, 1420 Object... objects) { 1421 String dateTag = getMessageTag(tag); 1422 1423 for (Object obj : objects) { 1424 stream.print(dateTag); 1425 if (obj instanceof String) { 1426 stream.println((String)obj); 1427 } else { 1428 stream.println(obj.toString()); 1429 } 1430 } 1431 } 1432 1433 /** 1434 * Creates a string containing the current date/time, and the tag 1435 * @param tag The tag associated to the message. Can be null 1436 * @return The dateTag 1437 */ 1438 public static String getMessageTag(String tag) { 1439 Calendar c = Calendar.getInstance(); 1440 1441 if (tag == null) { 1442 return String.format(Messages.Console_Date_Tag, c); 1443 } 1444 1445 return String.format(Messages.Console_Data_Project_Tag, c, tag); 1446 } 1447 1448 } 1449