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.ide.eclipse.adt.AdtPlugin;
20 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
21 import com.android.ide.eclipse.adt.internal.sdk.AdtConsoleSdkLog;
22 import com.android.sdkstats.DdmsPreferenceStore;
23 import com.android.sdkuilib.internal.repository.ui.AdtUpdateDialog;
24 
25 import org.eclipse.core.runtime.IStatus;
26 import org.eclipse.jface.resource.ImageDescriptor;
27 import org.eclipse.jface.wizard.Wizard;
28 import org.eclipse.swt.widgets.Display;
29 import org.eclipse.swt.widgets.Shell;
30 import org.eclipse.ui.IWorkbench;
31 import org.eclipse.ui.IWorkbenchWindow;
32 import org.eclipse.ui.PlatformUI;
33 
34 import java.io.File;
35 import java.util.HashSet;
36 import java.util.Set;
37 
38 /**
39  * Wizard shown on first start for new users: configure SDK location, accept or
40  * reject usage data collection, etc
41  */
42 public class WelcomeWizard extends Wizard {
43     private final DdmsPreferenceStore mStore;
44 
45     private WelcomeWizardPage mWelcomePage;
46     private UsagePermissionPage mUsagePage;
47 
48     private final boolean mShowWelcomePage;
49     private final boolean mShowUsagePage;
50 
51     /**
52      * Creates a new {@link WelcomeWizard}
53      *
54      * @param store preferences for usage statistics collection etc
55      * @param showInstallSdkPage show page to install SDK's
56      * @param showUsageOptinPage show page to get user consent for usage data collection
57      */
WelcomeWizard(DdmsPreferenceStore store, boolean showInstallSdkPage, boolean showUsageOptinPage)58     public WelcomeWizard(DdmsPreferenceStore store, boolean showInstallSdkPage,
59             boolean showUsageOptinPage) {
60         mStore = store;
61         mShowWelcomePage = showInstallSdkPage;
62         mShowUsagePage = showUsageOptinPage;
63 
64         setWindowTitle("Welcome to Android Development");
65         ImageDescriptor image = AdtPlugin.getImageDescriptor("icons/android-64.png"); //$NON-NLS-1$
66         setDefaultPageImageDescriptor(image);
67     }
68 
69     @Override
addPages()70     public void addPages() {
71         if (mShowWelcomePage) {
72             mWelcomePage = new WelcomeWizardPage();
73             addPage(mWelcomePage);
74         }
75 
76         // It's possible that the user has already run the command line tools
77         // such as ddms and has agreed to usage statistics collection, but has never
78         // run ADT which is why the wizard was opened. No need to ask again.
79         if (mShowUsagePage && !mStore.hasPingId()) {
80             mUsagePage = new UsagePermissionPage();
81             addPage(mUsagePage);
82         }
83     }
84 
85     @Override
performFinish()86     public boolean performFinish() {
87         if (mUsagePage != null) {
88             boolean isUsageCollectionApproved = mUsagePage.isUsageCollectionApproved();
89             DdmsPreferenceStore store = new DdmsPreferenceStore();
90 
91             // Workaround: Store a new ping id if one doesn't exist, regardless of
92             // whether usage statistics gathering is enabled, to ensure that ddms and
93             // ADT agree upon whether usage data collection is enabled. The reason this
94             // is necessary is that the Eclipse PreferenceStore optimizes out writing
95             // property values that equal their default values, and in our case, the
96             // default value for usage-collection is "false", so it just doesn't write
97             // it into the config file is the user opts out - which means that nothing
98             // is written in ddms.config. That works in the sense that the getter returns
99             // "usage collection"=false, but it doesn't work in the sense that it looks
100             // like the property has not yet been decided by the user. DDMS will look at
101             // the existence of a ping id to see whether we've already considered the
102             // question, so do the same here.
103             if (!store.hasPingId()) {
104                 store.generateNewPingId();
105             }
106 
107             store.setPingOptIn(isUsageCollectionApproved);
108         }
109 
110         if (mWelcomePage != null) {
111             // Read out wizard settings immediately; we will perform the actual work
112             // after the wizard window has been taken down and it's too late to read the
113             // settings then
114             final File path = mWelcomePage.getPath();
115             final boolean installCommon = mWelcomePage.isInstallCommon();
116             final boolean installLatest = mWelcomePage.isInstallLatest();
117             final boolean createNew = mWelcomePage.isCreateNew();
118 
119             // Perform installation asynchronously since it takes a while.
120             getShell().getDisplay().asyncExec(new Runnable() {
121                 @Override
122                 public void run() {
123                     if (createNew) {
124                         try {
125                             Set<Integer> apiLevels = new HashSet<Integer>();
126                             if (installCommon) {
127                                 apiLevels.add(8);
128                             }
129                             if (installLatest) {
130                                 apiLevels.add(AdtUpdateDialog.USE_MAX_REMOTE_API_LEVEL);
131                             }
132                             installSdk(path, apiLevels);
133                         } catch (Exception e) {
134                             AdtPlugin.logAndPrintError(e, "ADT Welcome Wizard",
135                                     "Installation failed");
136                         }
137                     }
138 
139                     // Set SDK path after installation since this will trigger a SDK refresh.
140                     AdtPrefs.getPrefs().setSdkLocation(path);
141                 }
142             });
143         }
144 
145         // The wizard always succeeds, even if installation fails or is aborted
146         return true;
147     }
148 
149     /**
150      * Trigger the install window. It will connect to the repository, display
151      * a confirmation window showing which packages are selected for install
152      * and display a progress dialog during installation.
153      */
installSdk(File path, Set<Integer> apiLevels)154     private boolean installSdk(File path, Set<Integer> apiLevels) {
155         if (!path.isDirectory()) {
156             if (!path.mkdirs()) {
157                 AdtPlugin.logAndPrintError(null, "ADT Welcome Wizard",
158                         "Failed to create directory %1$s",
159                         path.getAbsolutePath());
160                 return false;
161             }
162         }
163 
164         // Get a shell to use for the SDK installation. There are cases where getActiveShell
165         // returns null so attempt to obtain it through other means.
166         Display display = AdtPlugin.getDisplay();
167         Shell shell = display.getActiveShell();
168         if (shell == null) {
169             IWorkbench workbench = PlatformUI.getWorkbench();
170             IWorkbenchWindow window = workbench.getActiveWorkbenchWindow();
171             if (window != null) {
172                 shell = window.getShell();
173             }
174         }
175         boolean disposeShell = false;
176         if (shell == null) {
177             shell = new Shell(display);
178             AdtPlugin.log(IStatus.WARNING, "No parent shell for SDK installation dialog");
179             disposeShell = true;
180         }
181 
182         AdtUpdateDialog updater = new AdtUpdateDialog(
183                 shell,
184                 new AdtConsoleSdkLog(),
185                 path.getAbsolutePath());
186         // Note: we don't have to specify tools & platform-tools since they
187         // are required dependencies of any platform.
188         boolean result = updater.installNewSdk(apiLevels);
189 
190         // TODO: Install extra package here as well since it is now core to most of
191         // the templates
192         // if (result) {
193         //     updater.installExtraPackage(vendor, path);
194         // }
195 
196         if (disposeShell) {
197             shell.dispose();
198         }
199 
200         if (!result) {
201             AdtPlugin.printErrorToConsole("Failed to install Android SDK.");
202             return false;
203         }
204 
205         return true;
206     }
207 }
208