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 earlyStartup()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 isSdkSpecified()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 */ getBundledSdk()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 isFirstTime()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 handleError( CheckSdkErrorHandler.Solution solution, String message)170 public boolean handleError( 171 CheckSdkErrorHandler.Solution solution, 172 String message) { 173 return false; 174 } 175 176 @Override handleWarning( CheckSdkErrorHandler.Solution solution, String message)177 public boolean handleWarning( 178 CheckSdkErrorHandler.Solution solution, 179 String message) { 180 return true; 181 } 182 } 183 getSdkPathFromWindowsRegistry()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 initializeWindowCoordinator()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 showWelcomeWizard(final boolean showSdkInstallPage, final boolean showUsageOptInPage)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 sendUsageStats()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 */ createPingUsageServerJob()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 getVersion(Plugin plugin)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 */ pingUsageServer()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 windowActivated(IWorkbenchWindow window)347 public void windowActivated(IWorkbenchWindow window) { 348 } 349 350 @Override windowDeactivated(IWorkbenchWindow window)351 public void windowDeactivated(IWorkbenchWindow window) { 352 } 353 354 @Override windowClosed(IWorkbenchWindow window)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 windowOpened(IWorkbenchWindow window)363 public void windowOpened(IWorkbenchWindow window) { 364 LayoutWindowCoordinator.get(window, true /*create*/); 365 } 366 } 367