blob: b07893dc03cd9f34053f7bc846baba97f7038884 [file] [log] [blame]
/*
* 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*/);
}
}