diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/AdtStartup.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/AdtStartup.java | 366 |
1 files changed, 366 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/AdtStartup.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/AdtStartup.java new file mode 100644 index 000000000..b07893dc0 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/AdtStartup.java @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.welcome; + +import com.android.SdkConstants; +import com.android.annotations.Nullable; +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.AdtPlugin.CheckSdkErrorHandler; +import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutWindowCoordinator; +import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs; +import com.android.ide.eclipse.base.InstallDetails; +import com.android.utils.GrabProcessOutput; +import com.android.utils.GrabProcessOutput.IProcessOutput; +import com.android.utils.GrabProcessOutput.Wait; +import com.android.sdkstats.DdmsPreferenceStore; +import com.android.sdkstats.SdkStatsService; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Plugin; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.osgi.service.datalocation.Location; +import org.eclipse.ui.IStartup; +import org.eclipse.ui.IWindowListener; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.IWorkbenchWindow; +import org.eclipse.ui.PlatformUI; +import org.osgi.framework.Constants; +import org.osgi.framework.Version; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * ADT startup tasks (other than those performed in {@link AdtPlugin#start(org.osgi.framework.BundleContext)} + * when the plugin is initializing. + * <p> + * The main tasks currently performed are: + * <ul> + * <li> See if the user has ever run the welcome wizard, and if not, run it + * <li> Ping the usage statistics server, if enabled by the user. This is done here + * rather than during the plugin start since this task is run later (when the workspace + * is fully initialized) and we want to ask the user for permission for usage + * tracking before running it (and if we don't, then the usage tracking permissions + * dialog will run instead.) + * </ul> + */ +public class AdtStartup implements IStartup, IWindowListener { + + private DdmsPreferenceStore mStore = new DdmsPreferenceStore(); + + @Override + public void earlyStartup() { + if (!isSdkSpecified()) { + File bundledSdk = getBundledSdk(); + if (bundledSdk != null) { + AdtPrefs.getPrefs().setSdkLocation(bundledSdk); + } + } + + boolean showSdkInstallationPage = !isSdkSpecified() && isFirstTime(); + boolean showOptInDialogPage = !mStore.hasPingId(); + + if (showSdkInstallationPage || showOptInDialogPage) { + showWelcomeWizard(showSdkInstallationPage, showOptInDialogPage); + } + + if (mStore.isPingOptIn()) { + sendUsageStats(); + } + + initializeWindowCoordinator(); + + AdtPlugin.getDefault().workbenchStarted(); + } + + private boolean isSdkSpecified() { + String osSdkFolder = AdtPrefs.getPrefs().getOsSdkFolder(); + return (osSdkFolder != null && !osSdkFolder.isEmpty()); + } + + /** + * Returns the path to the bundled SDK if this is part of the ADT package. + * The ADT package has the following structure: + * root + * |--eclipse + * |--sdk + * @return path to bundled SDK, null if no valid bundled SDK detected. + */ + private File getBundledSdk() { + Location install = Platform.getInstallLocation(); + if (install != null && install.getURL() != null) { + File toolsFolder = new File(install.getURL().getFile()).getParentFile(); + if (toolsFolder != null) { + File sdkFolder = new File(toolsFolder, "sdk"); + if (sdkFolder.exists() && AdtPlugin.getDefault().checkSdkLocationAndId( + sdkFolder.getAbsolutePath(), + new SdkValidator())) { + return sdkFolder; + } + } + } + + return null; + } + + private boolean isFirstTime() { + for (int i = 0; i < 2; i++) { + String osSdkPath = null; + + if (i == 0) { + // If we've recorded an SDK location in the .android settings, then the user + // has run ADT before but possibly in a different workspace. We don't want to pop up + // the welcome wizard each time if we can simply use the existing SDK install. + osSdkPath = mStore.getLastSdkPath(); + } else if (i == 1) { + osSdkPath = getSdkPathFromWindowsRegistry(); + } + + if (osSdkPath != null && osSdkPath.length() > 0) { + boolean ok = new File(osSdkPath).isDirectory(); + + if (!ok) { + osSdkPath = osSdkPath.trim(); + ok = new File(osSdkPath).isDirectory(); + } + + if (ok) { + // Verify that the SDK is valid + ok = AdtPlugin.getDefault().checkSdkLocationAndId( + osSdkPath, new SdkValidator()); + if (ok) { + // Yes, we've seen an SDK location before and we can use it again, + // no need to pester the user with the welcome wizard. + // This also implies that the user has responded to the usage statistics + // question. + AdtPrefs.getPrefs().setSdkLocation(new File(osSdkPath)); + return false; + } + } + } + } + + // Check whether we've run this wizard before. + return !mStore.isAdtUsed(); + } + + private static class SdkValidator extends AdtPlugin.CheckSdkErrorHandler { + @Override + public boolean handleError( + CheckSdkErrorHandler.Solution solution, + String message) { + return false; + } + + @Override + public boolean handleWarning( + CheckSdkErrorHandler.Solution solution, + String message) { + return true; + } + } + + private String getSdkPathFromWindowsRegistry() { + if (SdkConstants.CURRENT_PLATFORM != SdkConstants.PLATFORM_WINDOWS) { + return null; + } + + final String valueName = "Path"; //$NON-NLS-1$ + final AtomicReference<String> result = new AtomicReference<String>(); + final Pattern regexp = + Pattern.compile("^\\s+" + valueName + "\\s+REG_SZ\\s+(.*)$");//$NON-NLS-1$ //$NON-NLS-2$ + + for (String key : new String[] { + "HKLM\\Software\\Android SDK Tools", //$NON-NLS-1$ + "HKLM\\Software\\Wow6432Node\\Android SDK Tools" }) { //$NON-NLS-1$ + + String[] command = new String[] { + "reg", "query", key, "/v", valueName //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + }; + + Process process; + try { + process = Runtime.getRuntime().exec(command); + + GrabProcessOutput.grabProcessOutput( + process, + Wait.WAIT_FOR_READERS, + new IProcessOutput() { + @Override + public void out(@Nullable String line) { + if (line != null) { + Matcher m = regexp.matcher(line); + if (m.matches()) { + result.set(m.group(1)); + } + } + } + + @Override + public void err(@Nullable String line) { + // ignore stderr + } + }); + } catch (IOException ignore) { + } catch (InterruptedException ignore) { + } + + String str = result.get(); + if (str != null) { + if (new File(str).isDirectory()) { + return str; + } + str = str.trim(); + if (new File(str).isDirectory()) { + return str; + } + } + } + + return null; + } + + private void initializeWindowCoordinator() { + final IWorkbench workbench = PlatformUI.getWorkbench(); + workbench.addWindowListener(this); + workbench.getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + for (IWorkbenchWindow window : workbench.getWorkbenchWindows()) { + LayoutWindowCoordinator.get(window, true /*create*/); + } + } + }); + } + + private void showWelcomeWizard(final boolean showSdkInstallPage, + final boolean showUsageOptInPage) { + final IWorkbench workbench = PlatformUI.getWorkbench(); + workbench.getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + IWorkbenchWindow window = workbench.getActiveWorkbenchWindow(); + if (window != null) { + WelcomeWizard wizard = new WelcomeWizard(mStore, showSdkInstallPage, + showUsageOptInPage); + WizardDialog dialog = new WizardDialog(window.getShell(), wizard); + dialog.open(); + } + + // Record the fact that we've run the wizard so we don't attempt to do it again, + // even if the user just cancels out of the wizard. + mStore.setAdtUsed(true); + + if (mStore.isPingOptIn()) { + sendUsageStats(); + } + } + }); + } + + private void sendUsageStats() { + // Ping the usage server and parse the SDK content. + // This is deferred in separate jobs to avoid blocking the bundle start. + // We also serialize them to avoid too many parallel jobs when Eclipse starts. + Job pingJob = createPingUsageServerJob(); + // build jobs are run after other interactive jobs + pingJob.setPriority(Job.BUILD); + // Wait another 30 seconds before starting the ping job. This gives other + // startup tasks time to finish since it's not vital to get the usage ping + // immediately. + pingJob.schedule(30000 /*milliseconds*/); + } + + /** + * Creates a job than can ping the usage server. + */ + private Job createPingUsageServerJob() { + // In order to not block the plugin loading, so we spawn another thread. + Job job = new Job("Android SDK Ping") { // Job name, visible in progress view + @Override + protected IStatus run(IProgressMonitor monitor) { + try { + pingUsageServer(); + + return Status.OK_STATUS; + } catch (Throwable t) { + AdtPlugin.log(t, "pingUsageServer failed"); //$NON-NLS-1$ + return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID, + "pingUsageServer failed", t); //$NON-NLS-1$ + } + } + }; + return job; + } + + private static Version getVersion(Plugin plugin) { + @SuppressWarnings("cast") // Cast required in Eclipse 3.5; prevent auto-removal in 3.7 + String version = (String) plugin.getBundle().getHeaders().get(Constants.BUNDLE_VERSION); + // Parse the string using the Version class. + return new Version(version); + } + + /** + * Pings the usage start server. + */ + private void pingUsageServer() { + // Report the version of the ADT plugin to the stat server + Version version = getVersion(AdtPlugin.getDefault()); + String adtVersionString = String.format("%1$d.%2$d.%3$d", version.getMajor(), //$NON-NLS-1$ + version.getMinor(), version.getMicro()); + + // Report the version of Eclipse to the stat server. + // Get the version of eclipse by getting the version of one of the runtime plugins. + Version eclipseVersion = InstallDetails.getPlatformVersion(); + String eclipseVersionString = String.format("%1$d.%2$d", //$NON-NLS-1$ + eclipseVersion.getMajor(), eclipseVersion.getMinor()); + + SdkStatsService stats = new SdkStatsService(); + stats.ping("adt", adtVersionString); //$NON-NLS-1$ + stats.ping("eclipse", eclipseVersionString); //$NON-NLS-1$ + } + + // ---- Implements IWindowListener ---- + + @Override + public void windowActivated(IWorkbenchWindow window) { + } + + @Override + public void windowDeactivated(IWorkbenchWindow window) { + } + + @Override + public void windowClosed(IWorkbenchWindow window) { + LayoutWindowCoordinator listener = LayoutWindowCoordinator.get(window, false /*create*/); + if (listener != null) { + listener.dispose(); + } + } + + @Override + public void windowOpened(IWorkbenchWindow window) { + LayoutWindowCoordinator.get(window, true /*create*/); + } +} |