Home | History | Annotate | Download | only in welcome
      1 /*
      2  * Copyright (C) 2011 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.internal.welcome;
     18 
     19 import com.android.SdkConstants;
     20 import com.android.annotations.Nullable;
     21 import com.android.ide.eclipse.adt.AdtPlugin;
     22 import com.android.ide.eclipse.adt.AdtPlugin.CheckSdkErrorHandler;
     23 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.LayoutWindowCoordinator;
     24 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
     25 import com.android.ide.eclipse.base.InstallDetails;
     26 import com.android.utils.GrabProcessOutput;
     27 import com.android.utils.GrabProcessOutput.IProcessOutput;
     28 import com.android.utils.GrabProcessOutput.Wait;
     29 import com.android.sdkstats.DdmsPreferenceStore;
     30 import com.android.sdkstats.SdkStatsService;
     31 
     32 import org.eclipse.core.runtime.IProgressMonitor;
     33 import org.eclipse.core.runtime.IStatus;
     34 import org.eclipse.core.runtime.Platform;
     35 import org.eclipse.core.runtime.Plugin;
     36 import org.eclipse.core.runtime.Status;
     37 import org.eclipse.core.runtime.jobs.Job;
     38 import org.eclipse.jface.wizard.WizardDialog;
     39 import org.eclipse.osgi.service.datalocation.Location;
     40 import org.eclipse.ui.IStartup;
     41 import org.eclipse.ui.IWindowListener;
     42 import org.eclipse.ui.IWorkbench;
     43 import org.eclipse.ui.IWorkbenchWindow;
     44 import org.eclipse.ui.PlatformUI;
     45 import org.osgi.framework.Constants;
     46 import org.osgi.framework.Version;
     47 
     48 import java.io.File;
     49 import java.io.IOException;
     50 import java.util.concurrent.atomic.AtomicReference;
     51 import java.util.regex.Matcher;
     52 import java.util.regex.Pattern;
     53 
     54 /**
     55  * ADT startup tasks (other than those performed in {@link AdtPlugin#start(org.osgi.framework.BundleContext)}
     56  * when the plugin is initializing.
     57  * <p>
     58  * The main tasks currently performed are:
     59  * <ul>
     60  *   <li> See if the user has ever run the welcome wizard, and if not, run it
     61  *   <li> Ping the usage statistics server, if enabled by the user. This is done here
     62  *       rather than during the plugin start since this task is run later (when the workspace
     63  *       is fully initialized) and we want to ask the user for permission for usage
     64  *       tracking before running it (and if we don't, then the usage tracking permissions
     65  *       dialog will run instead.)
     66  * </ul>
     67  */
     68 public class AdtStartup implements IStartup, IWindowListener {
     69 
     70     private DdmsPreferenceStore mStore = new DdmsPreferenceStore();
     71 
     72     @Override
     73     public void earlyStartup() {
     74         if (!isSdkSpecified()) {
     75             File bundledSdk = getBundledSdk();
     76             if (bundledSdk != null) {
     77                 AdtPrefs.getPrefs().setSdkLocation(bundledSdk);
     78             }
     79         }
     80 
     81         boolean showSdkInstallationPage = !isSdkSpecified() && isFirstTime();
     82         boolean showOptInDialogPage = !mStore.hasPingId();
     83 
     84         if (showSdkInstallationPage || showOptInDialogPage) {
     85             showWelcomeWizard(showSdkInstallationPage, showOptInDialogPage);
     86         }
     87 
     88         if (mStore.isPingOptIn()) {
     89             sendUsageStats();
     90         }
     91 
     92         initializeWindowCoordinator();
     93 
     94         AdtPlugin.getDefault().workbenchStarted();
     95     }
     96 
     97     private boolean isSdkSpecified() {
     98         String osSdkFolder = AdtPrefs.getPrefs().getOsSdkFolder();
     99         return (osSdkFolder != null && !osSdkFolder.isEmpty());
    100     }
    101 
    102     /**
    103      * Returns the path to the bundled SDK if this is part of the ADT package.
    104      * The ADT package has the following structure:
    105      *   root
    106      *      |--eclipse
    107      *      |--sdk
    108      * @return path to bundled SDK, null if no valid bundled SDK detected.
    109      */
    110     private File getBundledSdk() {
    111         Location install = Platform.getInstallLocation();
    112         if (install != null && install.getURL() != null) {
    113             File toolsFolder = new File(install.getURL().getFile()).getParentFile();
    114             if (toolsFolder != null) {
    115                 File sdkFolder = new File(toolsFolder, "sdk");
    116                 if (sdkFolder.exists() && AdtPlugin.getDefault().checkSdkLocationAndId(
    117                         sdkFolder.getAbsolutePath(),
    118                         new SdkValidator())) {
    119                     return sdkFolder;
    120                 }
    121             }
    122         }
    123 
    124         return null;
    125     }
    126 
    127     private boolean isFirstTime() {
    128         for (int i = 0; i < 2; i++) {
    129             String osSdkPath = null;
    130 
    131             if (i == 0) {
    132                 // If we've recorded an SDK location in the .android settings, then the user
    133                 // has run ADT before but possibly in a different workspace. We don't want to pop up
    134                 // the welcome wizard each time if we can simply use the existing SDK install.
    135                 osSdkPath = mStore.getLastSdkPath();
    136             } else if (i == 1) {
    137                 osSdkPath = getSdkPathFromWindowsRegistry();
    138             }
    139 
    140             if (osSdkPath != null && osSdkPath.length() > 0) {
    141                 boolean ok = new File(osSdkPath).isDirectory();
    142 
    143                 if (!ok) {
    144                     osSdkPath = osSdkPath.trim();
    145                     ok = new File(osSdkPath).isDirectory();
    146                 }
    147 
    148                 if (ok) {
    149                     // Verify that the SDK is valid
    150                     ok = AdtPlugin.getDefault().checkSdkLocationAndId(
    151                             osSdkPath, new SdkValidator());
    152                     if (ok) {
    153                         // Yes, we've seen an SDK location before and we can use it again,
    154                         // no need to pester the user with the welcome wizard.
    155                         // This also implies that the user has responded to the usage statistics
    156                         // question.
    157                         AdtPrefs.getPrefs().setSdkLocation(new File(osSdkPath));
    158                         return false;
    159                     }
    160                 }
    161             }
    162         }
    163 
    164         // Check whether we've run this wizard before.
    165         return !mStore.isAdtUsed();
    166     }
    167 
    168     private static class SdkValidator extends AdtPlugin.CheckSdkErrorHandler {
    169         @Override
    170         public boolean handleError(
    171                 CheckSdkErrorHandler.Solution solution,
    172                 String message) {
    173             return false;
    174         }
    175 
    176         @Override
    177         public boolean handleWarning(
    178                 CheckSdkErrorHandler.Solution  solution,
    179                 String message) {
    180             return true;
    181         }
    182     }
    183 
    184     private String getSdkPathFromWindowsRegistry() {
    185         if (SdkConstants.CURRENT_PLATFORM != SdkConstants.PLATFORM_WINDOWS) {
    186             return null;
    187         }
    188 
    189         final String valueName = "Path";                                               //$NON-NLS-1$
    190         final AtomicReference<String> result = new AtomicReference<String>();
    191         final Pattern regexp =
    192             Pattern.compile("^\\s+" + valueName + "\\s+REG_SZ\\s+(.*)$");//$NON-NLS-1$ //$NON-NLS-2$
    193 
    194         for (String key : new String[] {
    195                 "HKLM\\Software\\Android SDK Tools",                                   //$NON-NLS-1$
    196                 "HKLM\\Software\\Wow6432Node\\Android SDK Tools" }) {                  //$NON-NLS-1$
    197 
    198             String[] command = new String[] {
    199                 "reg", "query", key, "/v", valueName       //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    200             };
    201 
    202             Process process;
    203             try {
    204                 process = Runtime.getRuntime().exec(command);
    205 
    206                 GrabProcessOutput.grabProcessOutput(
    207                         process,
    208                         Wait.WAIT_FOR_READERS,
    209                         new IProcessOutput() {
    210                             @Override
    211                             public void out(@Nullable String line) {
    212                                 if (line != null) {
    213                                     Matcher m = regexp.matcher(line);
    214                                     if (m.matches()) {
    215                                         result.set(m.group(1));
    216                                     }
    217                                 }
    218                             }
    219 
    220                             @Override
    221                             public void err(@Nullable String line) {
    222                                 // ignore stderr
    223                             }
    224                         });
    225             } catch (IOException ignore) {
    226             } catch (InterruptedException ignore) {
    227             }
    228 
    229             String str = result.get();
    230             if (str != null) {
    231                 if (new File(str).isDirectory()) {
    232                     return str;
    233                 }
    234                 str = str.trim();
    235                 if (new File(str).isDirectory()) {
    236                     return str;
    237                 }
    238             }
    239         }
    240 
    241         return null;
    242     }
    243 
    244     private void initializeWindowCoordinator() {
    245         final IWorkbench workbench = PlatformUI.getWorkbench();
    246         workbench.addWindowListener(this);
    247         workbench.getDisplay().asyncExec(new Runnable() {
    248             @Override
    249             public void run() {
    250                 for (IWorkbenchWindow window : workbench.getWorkbenchWindows()) {
    251                     LayoutWindowCoordinator.get(window, true /*create*/);
    252                 }
    253             }
    254         });
    255     }
    256 
    257     private void showWelcomeWizard(final boolean showSdkInstallPage,
    258             final boolean showUsageOptInPage) {
    259         final IWorkbench workbench = PlatformUI.getWorkbench();
    260         workbench.getDisplay().asyncExec(new Runnable() {
    261             @Override
    262             public void run() {
    263                 IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
    264                 if (window != null) {
    265                     WelcomeWizard wizard = new WelcomeWizard(mStore, showSdkInstallPage,
    266                             showUsageOptInPage);
    267                     WizardDialog dialog = new WizardDialog(window.getShell(), wizard);
    268                     dialog.open();
    269                 }
    270 
    271                 // Record the fact that we've run the wizard so we don't attempt to do it again,
    272                 // even if the user just cancels out of the wizard.
    273                 mStore.setAdtUsed(true);
    274 
    275                 if (mStore.isPingOptIn()) {
    276                     sendUsageStats();
    277                 }
    278             }
    279         });
    280     }
    281 
    282     private void sendUsageStats() {
    283         // Ping the usage server and parse the SDK content.
    284         // This is deferred in separate jobs to avoid blocking the bundle start.
    285         // We also serialize them to avoid too many parallel jobs when Eclipse starts.
    286         Job pingJob = createPingUsageServerJob();
    287         // build jobs are run after other interactive jobs
    288         pingJob.setPriority(Job.BUILD);
    289         // Wait another 30 seconds before starting the ping job. This gives other
    290         // startup tasks time to finish since it's not vital to get the usage ping
    291         // immediately.
    292         pingJob.schedule(30000 /*milliseconds*/);
    293     }
    294 
    295     /**
    296      * Creates a job than can ping the usage server.
    297      */
    298     private Job createPingUsageServerJob() {
    299         // In order to not block the plugin loading, so we spawn another thread.
    300         Job job = new Job("Android SDK Ping") {  // Job name, visible in progress view
    301             @Override
    302             protected IStatus run(IProgressMonitor monitor) {
    303                 try {
    304                     pingUsageServer();
    305 
    306                     return Status.OK_STATUS;
    307                 } catch (Throwable t) {
    308                     AdtPlugin.log(t, "pingUsageServer failed");       //$NON-NLS-1$
    309                     return new Status(IStatus.ERROR, AdtPlugin.PLUGIN_ID,
    310                             "pingUsageServer failed", t);    //$NON-NLS-1$
    311                 }
    312             }
    313         };
    314         return job;
    315     }
    316 
    317     private static Version getVersion(Plugin plugin) {
    318         @SuppressWarnings("cast") // Cast required in Eclipse 3.5; prevent auto-removal in 3.7
    319         String version = (String) plugin.getBundle().getHeaders().get(Constants.BUNDLE_VERSION);
    320         // Parse the string using the Version class.
    321         return new Version(version);
    322     }
    323 
    324     /**
    325      * Pings the usage start server.
    326      */
    327     private void pingUsageServer() {
    328         // Report the version of the ADT plugin to the stat server
    329         Version version = getVersion(AdtPlugin.getDefault());
    330         String adtVersionString = String.format("%1$d.%2$d.%3$d", version.getMajor(), //$NON-NLS-1$
    331                 version.getMinor(), version.getMicro());
    332 
    333         // Report the version of Eclipse to the stat server.
    334         // Get the version of eclipse by getting the version of one of the runtime plugins.
    335         Version eclipseVersion = InstallDetails.getPlatformVersion();
    336         String eclipseVersionString = String.format("%1$d.%2$d",  //$NON-NLS-1$
    337                 eclipseVersion.getMajor(), eclipseVersion.getMinor());
    338 
    339         SdkStatsService stats = new SdkStatsService();
    340         stats.ping("adt", adtVersionString); //$NON-NLS-1$
    341         stats.ping("eclipse", eclipseVersionString); //$NON-NLS-1$
    342     }
    343 
    344     // ---- Implements IWindowListener ----
    345 
    346     @Override
    347     public void windowActivated(IWorkbenchWindow window) {
    348     }
    349 
    350     @Override
    351     public void windowDeactivated(IWorkbenchWindow window) {
    352     }
    353 
    354     @Override
    355     public void windowClosed(IWorkbenchWindow window) {
    356         LayoutWindowCoordinator listener = LayoutWindowCoordinator.get(window, false /*create*/);
    357         if (listener != null) {
    358             listener.dispose();
    359         }
    360     }
    361 
    362     @Override
    363     public void windowOpened(IWorkbenchWindow window) {
    364         LayoutWindowCoordinator.get(window, true /*create*/);
    365     }
    366 }
    367