| /* |
| * 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*/); |
| } |
| } |