aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/AdtStartup.java366
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/UsagePermissionPage.java148
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/WelcomeWizard.java207
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/WelcomeWizardPage.java302
4 files changed, 1023 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*/);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/UsagePermissionPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/UsagePermissionPage.java
new file mode 100644
index 000000000..d8413bb45
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/UsagePermissionPage.java
@@ -0,0 +1,148 @@
+/*
+ * 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.sdkstats.SdkStatsPermissionDialog;
+
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.dialogs.MessageDialog;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Link;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.browser.IWebBrowser;
+
+import java.net.URL;
+
+/** Page which displays the permission dialog for collecting usage statistics */
+public class UsagePermissionPage extends WizardPage implements SelectionListener {
+ private Link mLink;
+ private Button mYesRadio;
+ private Button mNoRadio;
+
+ /**
+ * Create the wizard.
+ */
+ public UsagePermissionPage() {
+ super("usageData");
+ setTitle("Contribute Usage Statistics?");
+ setDescription(SdkStatsPermissionDialog.NOTICE_TEXT);
+ }
+
+ /**
+ * Create contents of the wizard.
+ *
+ * @param parent parent to create page into
+ */
+ @Override
+ public void createControl(Composite parent) {
+ Composite container = new Composite(parent, SWT.NULL);
+
+ setControl(container);
+ container.setLayout(new GridLayout(1, false));
+
+ Label label = new Label(container, SWT.WRAP);
+ GridData gd_lblByChoosingTo = new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1);
+ gd_lblByChoosingTo.widthHint = 580;
+ label.setLayoutData(gd_lblByChoosingTo);
+ label.setText(SdkStatsPermissionDialog.BODY_TEXT);
+
+ Label blankLine = new Label(container, SWT.NONE);
+
+ Label questionLabel = new Label(container, SWT.NONE);
+ questionLabel.setText("Send usage statistics to Google?");
+
+ mYesRadio = new Button(container, SWT.RADIO);
+ mYesRadio.setText("Yes");
+ mYesRadio.addSelectionListener(this);
+
+ mNoRadio = new Button(container, SWT.RADIO);
+ mNoRadio.setText("No");
+ mNoRadio.addSelectionListener(this);
+
+ Label laterLabel = new Label(container, SWT.WRAP);
+ GridData gdLaterLabel = new GridData(SWT.FILL, SWT.BOTTOM, false, true, 1, 1);
+ gdLaterLabel.widthHint = 580;
+ laterLabel.setLayoutData(gdLaterLabel);
+ laterLabel.setText("If you later decide to change this setting, you can do so in the " +
+ "options panel under Android > Usage Stats");
+
+ mLink = new Link(container, SWT.NONE);
+ mLink.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
+ mLink.setText(SdkStatsPermissionDialog.PRIVACY_POLICY_LINK_TEXT);
+ mLink.addSelectionListener(this);
+
+ validatePage();
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ super.setVisible(visible);
+ mYesRadio.setFocus();
+ }
+
+ boolean isUsageCollectionApproved() {
+ return mYesRadio.getSelection();
+ }
+
+ @Override
+ public void widgetSelected(SelectionEvent event) {
+ if (event.getSource() == mLink) {
+ try {
+ IWorkbench workbench = PlatformUI.getWorkbench();
+ IWebBrowser browser = workbench.getBrowserSupport().getExternalBrowser();
+ browser.openURL(new URL(event.text));
+ } catch (Exception e) {
+ String message = String.format("Could not open browser. Vist\n%1$s\ninstead.",
+ event.text);
+ MessageDialog.openError(getWizard().getContainer().getShell(),
+ "Browser Error", message);
+ }
+ } else {
+ // Radio buttons selected
+ validatePage();
+ }
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+
+ private void validatePage() {
+ String error = null;
+
+ if (!mYesRadio.getSelection() && !mNoRadio.getSelection()) {
+ error = "Select Yes or No";
+ }
+
+ setPageComplete(error == null);
+ if (error != null) {
+ setMessage(error, IMessageProvider.ERROR);
+ } else {
+ setErrorMessage(null);
+ setMessage(null);
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/WelcomeWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/WelcomeWizard.java
new file mode 100644
index 000000000..916924e99
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/WelcomeWizard.java
@@ -0,0 +1,207 @@
+/*
+ * 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.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
+import com.android.ide.eclipse.adt.internal.sdk.AdtConsoleSdkLog;
+import com.android.sdkstats.DdmsPreferenceStore;
+import com.android.sdkuilib.internal.repository.ui.AdtUpdateDialog;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.jface.resource.ImageDescriptor;
+import org.eclipse.jface.wizard.Wizard;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Shell;
+import org.eclipse.ui.IWorkbench;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.PlatformUI;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Wizard shown on first start for new users: configure SDK location, accept or
+ * reject usage data collection, etc
+ */
+public class WelcomeWizard extends Wizard {
+ private final DdmsPreferenceStore mStore;
+
+ private WelcomeWizardPage mWelcomePage;
+ private UsagePermissionPage mUsagePage;
+
+ private final boolean mShowWelcomePage;
+ private final boolean mShowUsagePage;
+
+ /**
+ * Creates a new {@link WelcomeWizard}
+ *
+ * @param store preferences for usage statistics collection etc
+ * @param showInstallSdkPage show page to install SDK's
+ * @param showUsageOptinPage show page to get user consent for usage data collection
+ */
+ public WelcomeWizard(DdmsPreferenceStore store, boolean showInstallSdkPage,
+ boolean showUsageOptinPage) {
+ mStore = store;
+ mShowWelcomePage = showInstallSdkPage;
+ mShowUsagePage = showUsageOptinPage;
+
+ setWindowTitle("Welcome to Android Development");
+ ImageDescriptor image = AdtPlugin.getImageDescriptor("icons/android-64.png"); //$NON-NLS-1$
+ setDefaultPageImageDescriptor(image);
+ }
+
+ @Override
+ public void addPages() {
+ if (mShowWelcomePage) {
+ mWelcomePage = new WelcomeWizardPage();
+ addPage(mWelcomePage);
+ }
+
+ // It's possible that the user has already run the command line tools
+ // such as ddms and has agreed to usage statistics collection, but has never
+ // run ADT which is why the wizard was opened. No need to ask again.
+ if (mShowUsagePage && !mStore.hasPingId()) {
+ mUsagePage = new UsagePermissionPage();
+ addPage(mUsagePage);
+ }
+ }
+
+ @Override
+ public boolean performFinish() {
+ if (mUsagePage != null) {
+ boolean isUsageCollectionApproved = mUsagePage.isUsageCollectionApproved();
+ DdmsPreferenceStore store = new DdmsPreferenceStore();
+
+ // Workaround: Store a new ping id if one doesn't exist, regardless of
+ // whether usage statistics gathering is enabled, to ensure that ddms and
+ // ADT agree upon whether usage data collection is enabled. The reason this
+ // is necessary is that the Eclipse PreferenceStore optimizes out writing
+ // property values that equal their default values, and in our case, the
+ // default value for usage-collection is "false", so it just doesn't write
+ // it into the config file is the user opts out - which means that nothing
+ // is written in ddms.config. That works in the sense that the getter returns
+ // "usage collection"=false, but it doesn't work in the sense that it looks
+ // like the property has not yet been decided by the user. DDMS will look at
+ // the existence of a ping id to see whether we've already considered the
+ // question, so do the same here.
+ if (!store.hasPingId()) {
+ store.generateNewPingId();
+ }
+
+ store.setPingOptIn(isUsageCollectionApproved);
+ }
+
+ if (mWelcomePage != null) {
+ // Read out wizard settings immediately; we will perform the actual work
+ // after the wizard window has been taken down and it's too late to read the
+ // settings then
+ final File path = mWelcomePage.getPath();
+ final boolean installCommon = mWelcomePage.isInstallCommon();
+ final boolean installLatest = mWelcomePage.isInstallLatest();
+ final boolean createNew = mWelcomePage.isCreateNew();
+
+ // Perform installation asynchronously since it takes a while.
+ getShell().getDisplay().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ if (createNew) {
+ try {
+ Set<Integer> apiLevels = new HashSet<Integer>();
+ if (installCommon) {
+ apiLevels.add(8);
+ }
+ if (installLatest) {
+ apiLevels.add(AdtUpdateDialog.USE_MAX_REMOTE_API_LEVEL);
+ }
+ installSdk(path, apiLevels);
+ } catch (Exception e) {
+ AdtPlugin.logAndPrintError(e, "ADT Welcome Wizard",
+ "Installation failed");
+ }
+ }
+
+ // Set SDK path after installation since this will trigger a SDK refresh.
+ AdtPrefs.getPrefs().setSdkLocation(path);
+ }
+ });
+ }
+
+ // The wizard always succeeds, even if installation fails or is aborted
+ return true;
+ }
+
+ /**
+ * Trigger the install window. It will connect to the repository, display
+ * a confirmation window showing which packages are selected for install
+ * and display a progress dialog during installation.
+ */
+ private boolean installSdk(File path, Set<Integer> apiLevels) {
+ if (!path.isDirectory()) {
+ if (!path.mkdirs()) {
+ AdtPlugin.logAndPrintError(null, "ADT Welcome Wizard",
+ "Failed to create directory %1$s",
+ path.getAbsolutePath());
+ return false;
+ }
+ }
+
+ // Get a shell to use for the SDK installation. There are cases where getActiveShell
+ // returns null so attempt to obtain it through other means.
+ Display display = AdtPlugin.getDisplay();
+ Shell shell = display.getActiveShell();
+ if (shell == null) {
+ IWorkbench workbench = PlatformUI.getWorkbench();
+ IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
+ if (window != null) {
+ shell = window.getShell();
+ }
+ }
+ boolean disposeShell = false;
+ if (shell == null) {
+ shell = new Shell(display);
+ AdtPlugin.log(IStatus.WARNING, "No parent shell for SDK installation dialog");
+ disposeShell = true;
+ }
+
+ AdtUpdateDialog updater = new AdtUpdateDialog(
+ shell,
+ new AdtConsoleSdkLog(),
+ path.getAbsolutePath());
+ // Note: we don't have to specify tools & platform-tools since they
+ // are required dependencies of any platform.
+ boolean result = updater.installNewSdk(apiLevels);
+
+ // TODO: Install extra package here as well since it is now core to most of
+ // the templates
+ // if (result) {
+ // updater.installExtraPackage(vendor, path);
+ // }
+
+ if (disposeShell) {
+ shell.dispose();
+ }
+
+ if (!result) {
+ AdtPlugin.printErrorToConsole("Failed to install Android SDK.");
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/WelcomeWizardPage.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/WelcomeWizardPage.java
new file mode 100644
index 000000000..bcee88703
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/welcome/WelcomeWizardPage.java
@@ -0,0 +1,302 @@
+/*
+ * 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.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.AdtPlugin.CheckSdkErrorHandler;
+
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.DirectoryDialog;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+
+import java.io.File;
+import java.util.concurrent.atomic.AtomicReference;
+
+/** Main page shown in the {@link WelcomeWizard} */
+public class WelcomeWizardPage extends WizardPage implements ModifyListener, SelectionListener {
+ private Text mExistingDirText;
+ private Button mExistingDirButton;
+ private Button mInstallLatestCheckbox;
+ private Button mInstallCommonCheckbox;
+ private Button mInstallNewRadio;
+ private Button mUseExistingRadio;
+ private Text mNewDirText;
+ private Button mNewDirButton;
+
+ /**
+ * Create the wizard.
+ */
+ public WelcomeWizardPage() {
+ super("welcomePage");
+ setTitle("Welcome to Android Development");
+ setDescription("Configure SDK");
+ }
+
+ /**
+ * Create contents of the wizard.
+ * @param parent parent widget to add page to
+ */
+ @Override
+ @SuppressWarnings("unused") // SWT constructors have side effects so "new Label" is not unused
+ public void createControl(Composite parent) {
+ Composite container = new Composite(parent, SWT.NULL);
+
+ setControl(container);
+ container.setLayout(new GridLayout(4, false));
+
+ Label overviewLabel = new Label(container, SWT.WRAP | SWT.SHADOW_NONE);
+ GridData gdOverviewLabel = new GridData(SWT.FILL, SWT.CENTER, false, false, 4, 1);
+ gdOverviewLabel.widthHint = 580;
+ overviewLabel.setLayoutData(gdOverviewLabel);
+ overviewLabel.setText("To develop for Android, you need an Android SDK, and at least one version of the Android APIs to compile against. You may also want additional versions of Android to test with.");
+
+ Label spacing = new Label(container, SWT.NONE);
+ spacing.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 4, 1));
+
+ mInstallNewRadio = new Button(container, SWT.RADIO);
+ mInstallNewRadio.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 4, 1));
+ mInstallNewRadio.setSelection(true);
+ mInstallNewRadio.setText("Install new SDK");
+ mInstallNewRadio.addSelectionListener(this);
+
+ Label indentLabel = new Label(container, SWT.NONE);
+ GridData gdIndentLabel = new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1);
+ gdIndentLabel.widthHint = 20;
+ indentLabel.setLayoutData(gdIndentLabel);
+
+ mInstallLatestCheckbox = new Button(container, SWT.CHECK);
+ mInstallLatestCheckbox.setSelection(true);
+ mInstallLatestCheckbox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3,
+ 1));
+ mInstallLatestCheckbox.setText("Install the latest available version of Android APIs (supports all the latest features)");
+ mInstallLatestCheckbox.addSelectionListener(this);
+
+ new Label(container, SWT.NONE);
+ mInstallCommonCheckbox = new Button(container, SWT.CHECK);
+ mInstallCommonCheckbox.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3,
+ 1));
+ mInstallCommonCheckbox.setText("Install Android 2.2, a version which is supported by ~96% phones and tablets");
+ mInstallCommonCheckbox.addSelectionListener(this);
+
+ new Label(container, SWT.NONE);
+ Label addHintLabel = new Label(container, SWT.NONE);
+ addHintLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 3, 1));
+ addHintLabel.setText(" (You can add additional platforms using the SDK Manager.)");
+
+ new Label(container, SWT.NONE);
+ Label targetLabel = new Label(container, SWT.NONE);
+ targetLabel.setText("Target Location:");
+
+ mNewDirText = new Text(container, SWT.BORDER);
+ mNewDirText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ String defaultPath = System.getProperty("user.home") + File.separator + "android-sdks"; //$NON-NLS-1$
+ mNewDirText.setText(defaultPath);
+ mNewDirText.addModifyListener(this);
+
+ mNewDirButton = new Button(container, SWT.FLAT);
+ mNewDirButton.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false, 1, 1));
+ mNewDirButton.setText("Browse...");
+ mNewDirButton.addSelectionListener(this);
+
+ Label spacing2 = new Label(container, SWT.NONE);
+ spacing2.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 4, 1));
+
+ mUseExistingRadio = new Button(container, SWT.RADIO);
+ mUseExistingRadio.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 4, 1));
+ mUseExistingRadio.setText("Use existing SDKs");
+ mUseExistingRadio.addSelectionListener(this);
+
+ new Label(container, SWT.NONE);
+ Label installationLabel = new Label(container, SWT.NONE);
+ installationLabel.setText("Existing Location:");
+
+ mExistingDirText = new Text(container, SWT.BORDER);
+ mExistingDirText.setEnabled(false);
+ mExistingDirText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
+ mExistingDirText.addModifyListener(this);
+
+ mExistingDirButton = new Button(container, SWT.FLAT);
+ mExistingDirButton.setEnabled(false);
+ mExistingDirButton.setText("Browse...");
+ mExistingDirButton.addSelectionListener(this);
+ }
+
+ boolean isCreateNew() {
+ return mInstallNewRadio.getSelection();
+ }
+
+ boolean isInstallLatest() {
+ return mInstallLatestCheckbox.getSelection();
+ }
+
+ boolean isInstallCommon() {
+ return mInstallCommonCheckbox.getSelection();
+ }
+
+ File getPath() {
+ Text text = isCreateNew() ? mNewDirText : mExistingDirText;
+ return new File(text.getText());
+ }
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ Object source = e.getSource();
+
+ if (source == mExistingDirButton) {
+ DirectoryDialog dialog = new DirectoryDialog(mExistingDirButton.getShell(), SWT.OPEN);
+ String file = dialog.open();
+ String path = mExistingDirText.getText().trim();
+ if (path.length() > 0) {
+ // TODO: Shouldn't this be done before the open() call?
+ dialog.setFilterPath(path);
+ }
+ if (file != null) {
+ mExistingDirText.setText(file);
+ }
+ } else if (source == mNewDirButton) {
+ DirectoryDialog dialog = new DirectoryDialog(mNewDirButton.getShell(), SWT.OPEN);
+ String path = mNewDirText.getText().trim();
+ if (path.length() > 0) {
+ dialog.setFilterPath(path);
+ }
+ String file = dialog.open();
+ if (file != null) {
+ mNewDirText.setText(file);
+ }
+ } else if (source == mInstallNewRadio) {
+ mExistingDirButton.setEnabled(false);
+ mExistingDirText.setEnabled(false);
+ mNewDirButton.setEnabled(true);
+ mNewDirText.setEnabled(true);
+ } else if (source == mUseExistingRadio) {
+ mExistingDirButton.setEnabled(true);
+ mExistingDirText.setEnabled(true);
+ mNewDirButton.setEnabled(false);
+ mNewDirText.setEnabled(false);
+ }
+
+ validatePage();
+ }
+
+ @Override
+ public void widgetDefaultSelected(SelectionEvent e) {
+ }
+
+ @Override
+ public void modifyText(ModifyEvent e) {
+ validatePage();
+ }
+
+ private void validatePage() {
+ String error = null;
+ String warning = null;
+
+ if (isCreateNew()) {
+ // Make sure that the target installation directory is empty or doesn't exist
+ // (and that it can be created)
+ String path = mNewDirText.getText().trim();
+ if (path.length() == 0) {
+ error = "Please enter a new directory to install the SDK into";
+ } else {
+ File file = new File(path);
+ if (file.exists()) {
+ if (file.isDirectory()) {
+ if (!file.canWrite()) {
+ error = "Missing write permission in target directory";
+ }
+ File[] children = file.listFiles();
+ if (children != null && children.length > 0) {
+ warning = "The directory is not empty";
+ }
+ } else {
+ error = "The target must be a directory";
+ }
+ } else {
+ File parent = file.getParentFile();
+ if (parent == null || !parent.exists()) {
+ error = "The parent directory does not exist";
+ } else if (!parent.canWrite()) {
+ error = "No write permission in parent directory";
+ }
+ }
+ }
+
+ if (error == null && !mInstallLatestCheckbox.getSelection()
+ && !mInstallCommonCheckbox.getSelection()) {
+ error = "You must choose at least one Android version to install";
+ }
+ } else {
+ // Make sure that the existing installation directory exists and is valid
+ String path = mExistingDirText.getText().trim();
+ if (path.length() == 0) {
+ error = "Please enter an existing SDK installation directory";
+ } else {
+ File file = new File(path);
+ if (!file.exists()) {
+ error = "The chosen installation directory does not exist";
+ } else {
+ final AtomicReference<String> errorReference = new AtomicReference<String>();
+ final AtomicReference<String> warningReference = new AtomicReference<String>();
+ AdtPlugin.getDefault().checkSdkLocationAndId(path,
+ new AdtPlugin.CheckSdkErrorHandler() {
+ @Override
+ public boolean handleError(
+ CheckSdkErrorHandler.Solution solution,
+ String message) {
+ message = message.replaceAll("\n", " "); //$NON-NLS-1$ //$NON-NLS-2$
+ errorReference.set(message);
+ return false; // Apply/OK must be disabled
+ }
+
+ @Override
+ public boolean handleWarning(
+ CheckSdkErrorHandler.Solution solution,
+ String message) {
+ message = message.replaceAll("\n", " "); //$NON-NLS-1$ //$NON-NLS-2$
+ warningReference.set(message);
+ return true; // Apply/OK must be enabled
+ }
+ });
+ error = errorReference.get();
+ if (warning == null) {
+ warning = warningReference.get();
+ }
+ }
+ }
+ }
+
+ setPageComplete(error == null);
+ if (error != null) {
+ setMessage(error, IMessageProvider.ERROR);
+ } else if (warning != null) {
+ setMessage(warning, IMessageProvider.WARNING);
+ } else {
+ setErrorMessage(null);
+ setMessage(null);
+ }
+ }
+}