• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
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.sdkuilib.internal.repository;
18 
19 import com.android.annotations.VisibleForTesting;
20 import com.android.annotations.VisibleForTesting.Visibility;
21 import com.android.prefs.AndroidLocation.AndroidLocationException;
22 import com.android.sdklib.ISdkLog;
23 import com.android.sdklib.SdkConstants;
24 import com.android.sdklib.SdkManager;
25 import com.android.sdklib.internal.avd.AvdManager;
26 import com.android.sdklib.internal.repository.AdbWrapper;
27 import com.android.sdklib.internal.repository.AddonPackage;
28 import com.android.sdklib.internal.repository.AddonsListFetcher;
29 import com.android.sdklib.internal.repository.Archive;
30 import com.android.sdklib.internal.repository.ArchiveInstaller;
31 import com.android.sdklib.internal.repository.ITask;
32 import com.android.sdklib.internal.repository.ITaskFactory;
33 import com.android.sdklib.internal.repository.ITaskMonitor;
34 import com.android.sdklib.internal.repository.LocalSdkParser;
35 import com.android.sdklib.internal.repository.NullTaskMonitor;
36 import com.android.sdklib.internal.repository.Package;
37 import com.android.sdklib.internal.repository.PlatformToolPackage;
38 import com.android.sdklib.internal.repository.SdkAddonSource;
39 import com.android.sdklib.internal.repository.SdkRepoSource;
40 import com.android.sdklib.internal.repository.SdkSource;
41 import com.android.sdklib.internal.repository.SdkSourceCategory;
42 import com.android.sdklib.internal.repository.SdkSources;
43 import com.android.sdklib.internal.repository.ToolPackage;
44 import com.android.sdklib.internal.repository.AddonsListFetcher.Site;
45 import com.android.sdklib.repository.SdkAddonConstants;
46 import com.android.sdklib.repository.SdkAddonsListConstants;
47 import com.android.sdklib.repository.SdkRepoConstants;
48 import com.android.sdklib.util.LineUtil;
49 import com.android.sdklib.util.SparseIntArray;
50 import com.android.sdkuilib.internal.repository.icons.ImageFactory;
51 import com.android.sdkuilib.internal.repository.sdkman1.LocalSdkAdapter;
52 import com.android.sdkuilib.internal.repository.sdkman1.RemotePackagesPage;
53 import com.android.sdkuilib.internal.repository.sdkman1.RepoSourcesAdapter;
54 import com.android.sdkuilib.internal.repository.sdkman1.SdkUpdaterWindowImpl1;
55 import com.android.sdkuilib.repository.ISdkChangeListener;
56 
57 import org.eclipse.jface.dialogs.MessageDialog;
58 import org.eclipse.swt.widgets.Shell;
59 
60 import java.io.ByteArrayOutputStream;
61 import java.io.PrintStream;
62 import java.util.ArrayList;
63 import java.util.Collection;
64 import java.util.Collections;
65 import java.util.Comparator;
66 import java.util.HashMap;
67 import java.util.HashSet;
68 import java.util.Iterator;
69 import java.util.List;
70 import java.util.Map;
71 import java.util.Set;
72 
73 /**
74  * Data shared between {@link SdkUpdaterWindowImpl1} and its pages.
75  */
76 public class UpdaterData implements IUpdaterData {
77 
78     public static final int NO_TOOLS_MSG = 0;
79     public static final int TOOLS_MSG_UPDATED_FROM_ADT = 1;
80     public static final int TOOLS_MSG_UPDATED_FROM_SDKMAN = 2;
81 
82     private String mOsSdkRoot;
83 
84     private final ISdkLog mSdkLog;
85     private ITaskFactory mTaskFactory;
86 
87     private SdkManager mSdkManager;
88     private AvdManager mAvdManager;
89 
90     private final LocalSdkParser mLocalSdkParser = new LocalSdkParser();
91     private final SdkSources mSources = new SdkSources();
92 
93     private final LocalSdkAdapter mLocalSdkAdapter = new LocalSdkAdapter(this);
94     private final RepoSourcesAdapter mSourcesAdapter = new RepoSourcesAdapter(this);
95 
96     private ImageFactory mImageFactory;
97 
98     private final SettingsController mSettingsController;
99 
100     private final ArrayList<ISdkChangeListener> mListeners = new ArrayList<ISdkChangeListener>();
101 
102     private Shell mWindowShell;
103 
104     private AndroidLocationException mAvdManagerInitError;
105 
106     /**
107      * 0 = need to fetch remote addons list once..
108      * 1 = fetch succeeded, don't need to do it any more.
109      * -1= fetch failed, do it again only if the user requests a refresh
110      *     or changes the force-http setting.
111      */
112     private int mStateFetchRemoteAddonsList;
113 
114     /**
115      * Creates a new updater data.
116      *
117      * @param sdkLog Logger. Cannot be null.
118      * @param osSdkRoot The OS path to the SDK root.
119      */
UpdaterData(String osSdkRoot, ISdkLog sdkLog)120     public UpdaterData(String osSdkRoot, ISdkLog sdkLog) {
121         mOsSdkRoot = osSdkRoot;
122         mSdkLog = sdkLog;
123 
124         mSettingsController = new SettingsController(this);
125 
126         initSdk();
127     }
128 
129     // ----- getters, setters ----
130 
getOsSdkRoot()131     public String getOsSdkRoot() {
132         return mOsSdkRoot;
133     }
134 
setTaskFactory(ITaskFactory taskFactory)135     public void setTaskFactory(ITaskFactory taskFactory) {
136         mTaskFactory = taskFactory;
137     }
138 
getTaskFactory()139     public ITaskFactory getTaskFactory() {
140         return mTaskFactory;
141     }
142 
getSources()143     public SdkSources getSources() {
144         return mSources;
145     }
146 
getSourcesAdapter()147     public RepoSourcesAdapter getSourcesAdapter() {
148         return mSourcesAdapter;
149     }
150 
getLocalSdkParser()151     public LocalSdkParser getLocalSdkParser() {
152         return mLocalSdkParser;
153     }
154 
getLocalSdkAdapter()155     public LocalSdkAdapter getLocalSdkAdapter() {
156         return mLocalSdkAdapter;
157     }
158 
getSdkLog()159     public ISdkLog getSdkLog() {
160         return mSdkLog;
161     }
162 
setImageFactory(ImageFactory imageFactory)163     public void setImageFactory(ImageFactory imageFactory) {
164         mImageFactory = imageFactory;
165     }
166 
getImageFactory()167     public ImageFactory getImageFactory() {
168         return mImageFactory;
169     }
170 
getSdkManager()171     public SdkManager getSdkManager() {
172         return mSdkManager;
173     }
174 
getAvdManager()175     public AvdManager getAvdManager() {
176         return mAvdManager;
177     }
178 
getSettingsController()179     public SettingsController getSettingsController() {
180         return mSettingsController;
181     }
182 
183     /** Adds a listener ({@link ISdkChangeListener}) that is notified when the SDK is reloaded. */
addListeners(ISdkChangeListener listener)184     public void addListeners(ISdkChangeListener listener) {
185         if (mListeners.contains(listener) == false) {
186             mListeners.add(listener);
187         }
188     }
189 
190     /** Removes a listener ({@link ISdkChangeListener}) that is notified when the SDK is reloaded. */
removeListener(ISdkChangeListener listener)191     public void removeListener(ISdkChangeListener listener) {
192         mListeners.remove(listener);
193     }
194 
setWindowShell(Shell windowShell)195     public void setWindowShell(Shell windowShell) {
196         mWindowShell = windowShell;
197     }
198 
getWindowShell()199     public Shell getWindowShell() {
200         return mWindowShell;
201     }
202 
203     /**
204      * Check if any error occurred during initialization.
205      * If it did, display an error message.
206      *
207      * @return True if an error occurred, false if we should continue.
208      */
checkIfInitFailed()209     public boolean checkIfInitFailed() {
210         if (mAvdManagerInitError != null) {
211             String example;
212             if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) {
213                 example = "%USERPROFILE%";     //$NON-NLS-1$
214             } else {
215                 example = "~";                 //$NON-NLS-1$
216             }
217 
218             String error = String.format(
219                 "The AVD manager normally uses the user's profile directory to store " +
220                 "AVD files. However it failed to find the default profile directory. " +
221                 "\n" +
222                 "To fix this, please set the environment variable ANDROID_SDK_HOME to " +
223                 "a valid path such as \"%s\".",
224                 example);
225 
226             // We may not have any UI. Only display a dialog if there's a window shell available.
227             if (mWindowShell != null) {
228                 MessageDialog.openError(mWindowShell,
229                     "Android Virtual Devices Manager",
230                     error);
231             } else {
232                 mSdkLog.error(null /* Throwable */, "%s", error);  //$NON-NLS-1$
233             }
234 
235             return true;
236         }
237         return false;
238     }
239 
240     // -----
241 
242     /**
243      * Initializes the {@link SdkManager} and the {@link AvdManager}.
244      */
245     @VisibleForTesting(visibility=Visibility.PRIVATE)
initSdk()246     protected void initSdk() {
247         setSdkManager(SdkManager.createManager(mOsSdkRoot, mSdkLog));
248         try {
249             mAvdManager = null; // remove the old one if needed.
250             mAvdManager = new AvdManager(mSdkManager, mSdkLog);
251         } catch (AndroidLocationException e) {
252             mSdkLog.error(e, "Unable to read AVDs: " + e.getMessage());  //$NON-NLS-1$
253 
254             // Note: we used to continue here, but the thing is that
255             // mAvdManager==null so nothing is really going to work as
256             // expected. Let's just display an error later in checkIfInitFailed()
257             // and abort right there. This step is just too early in the SWT
258             // setup process to display a message box yet.
259 
260             mAvdManagerInitError = e;
261         }
262 
263         // notify listeners.
264         broadcastOnSdkReload();
265     }
266 
267     @VisibleForTesting(visibility=Visibility.PRIVATE)
setSdkManager(SdkManager sdkManager)268     protected void setSdkManager(SdkManager sdkManager) {
269         mSdkManager = sdkManager;
270     }
271 
272     /**
273      * Reloads the SDK content (targets).
274      * <p/>
275      * This also reloads the AVDs in case their status changed.
276      * <p/>
277      * This does not notify the listeners ({@link ISdkChangeListener}).
278      */
reloadSdk()279     public void reloadSdk() {
280         // reload SDK
281         mSdkManager.reloadSdk(mSdkLog);
282 
283         // reload AVDs
284         if (mAvdManager != null) {
285             try {
286                 mAvdManager.reloadAvds(mSdkLog);
287             } catch (AndroidLocationException e) {
288                 // FIXME
289             }
290         }
291 
292         mLocalSdkParser.clearPackages();
293 
294         // notify listeners
295         broadcastOnSdkReload();
296     }
297 
298     /**
299      * Reloads the AVDs.
300      * <p/>
301      * This does not notify the listeners.
302      */
reloadAvds()303     public void reloadAvds() {
304         // reload AVDs
305         if (mAvdManager != null) {
306             try {
307                 mAvdManager.reloadAvds(mSdkLog);
308             } catch (AndroidLocationException e) {
309                 mSdkLog.error(e, null);
310             }
311         }
312     }
313 
314     /**
315      * Sets up the default sources: <br/>
316      * - the default google SDK repository, <br/>
317      * - the user sources from prefs <br/>
318      * - the extra repo URLs from the environment, <br/>
319      * - and finally the extra user repo URLs from the environment.
320      * <p/>
321      * Note that the "remote add-ons" list is not loaded from here. Instead
322      * it is fetched the first time the {@link RemotePackagesPage} is displayed.
323      */
setupDefaultSources()324     public void setupDefaultSources() {
325         SdkSources sources = getSources();
326 
327         // SDK_TEST_URLS is a semicolon-separated list of URLs that can be used to
328         // seed the SDK Updater list for full repos and addon repositories. This is
329         // only meant as a debugging and QA testing tool and not for user usage.
330         //
331         // To be used, the URLs must either end with the / or end with the canonical
332         // filename expected for either a full repo or an add-on repo. This lets QA
333         // use URLs ending with / to cover all cases.
334         String testUrls = System.getenv("SDK_TEST_URLS");
335         if (testUrls != null) {
336             String[] urls = testUrls.split(";");
337             for (String url : urls) {
338                 if (url != null) {
339                     url = url.trim();
340                     if (url.endsWith("/")
341                             || url.endsWith(SdkRepoConstants.URL_DEFAULT_FILENAME)
342                             || url.endsWith(SdkRepoConstants.URL_DEFAULT_FILENAME2)) {
343                         String fullUrl = url;
344                         if (fullUrl.endsWith("/")) {
345                             fullUrl += SdkRepoConstants.URL_DEFAULT_FILENAME2;
346                         }
347 
348                         SdkSource s = new SdkRepoSource(fullUrl, null/*uiName*/);
349                         if (!sources.hasSourceUrl(s)) {
350                             sources.add(SdkSourceCategory.GETENV_REPOS, s);
351                         }
352                     }
353 
354                     if (url.endsWith("/") || url.endsWith(SdkAddonConstants.URL_DEFAULT_FILENAME)) {
355                         String fullUrl = url;
356                         if (fullUrl.endsWith("/")) {
357                             fullUrl += SdkAddonConstants.URL_DEFAULT_FILENAME;
358                         }
359 
360                         SdkSource s = new SdkAddonSource(fullUrl, null/*uiName*/);
361                         if (!sources.hasSourceUrl(s)) {
362                             sources.add(SdkSourceCategory.GETENV_ADDONS, s);
363                         }
364                     }
365                 }
366             }
367         }
368 
369         // Load the conventional sources if we didn't load anything or if
370         // there's an env var asking to do so anyway.
371         if (sources.getAllSources().length == 0 ||
372                 System.getenv("SDK_MIX_WITH_TEST_URLS") != null) {
373             sources.add(SdkSourceCategory.ANDROID_REPO,
374                     new SdkRepoSource(SdkRepoConstants.URL_GOOGLE_SDK_SITE,
375                                       SdkSourceCategory.ANDROID_REPO.getUiName()));
376 
377             // Load user sources
378             sources.loadUserAddons(getSdkLog());
379         }
380     }
381 
382     /**
383      * Returns the list of installed packages, parsing them if this has not yet been done.
384      * <p/>
385      * The package list is cached in the {@link LocalSdkParser} and will be reset when
386      * {@link #reloadSdk()} is invoked.
387      */
getInstalledPackages(ITaskMonitor monitor)388     public Package[] getInstalledPackages(ITaskMonitor monitor) {
389         LocalSdkParser parser = getLocalSdkParser();
390 
391         Package[] packages = parser.getPackages();
392 
393         if (packages == null) {
394             // load on demand the first time
395             packages = parser.parseSdk(getOsSdkRoot(), getSdkManager(), monitor);
396         }
397 
398         return packages;
399     }
400     /**
401      * Install the list of given {@link Archive}s. This is invoked by the user selecting some
402      * packages in the remote page and then clicking "install selected".
403      *
404      * @param archives The archives to install. Incompatible ones will be skipped.
405      * @param flags Optional flags for the installer, such as {@link #NO_TOOLS_MSG}.
406      * @return A list of archives that have been installed. Can be empty but not null.
407      */
408     @VisibleForTesting(visibility=Visibility.PRIVATE)
installArchives(final List<ArchiveInfo> archives, final int flags)409     protected List<Archive> installArchives(final List<ArchiveInfo> archives, final int flags) {
410         if (mTaskFactory == null) {
411             throw new IllegalArgumentException("Task Factory is null");
412         }
413 
414         // this will accumulate all the packages installed.
415         final List<Archive> newlyInstalledArchives = new ArrayList<Archive>();
416 
417         final boolean forceHttp = getSettingsController().getForceHttp();
418 
419         // sort all archives based on their dependency level.
420         Collections.sort(archives, new InstallOrderComparator());
421 
422         mTaskFactory.start("Installing Archives", new ITask() {
423             public void run(ITaskMonitor monitor) {
424 
425                 final int progressPerArchive = 2 * ArchiveInstaller.NUM_MONITOR_INC;
426                 monitor.setProgressMax(1 + archives.size() * progressPerArchive);
427                 monitor.setDescription("Preparing to install archives");
428 
429                 boolean installedAddon = false;
430                 boolean installedTools = false;
431                 boolean installedPlatformTools = false;
432                 boolean preInstallHookInvoked = false;
433 
434                 // Mark all current local archives as already installed.
435                 HashSet<Archive> installedArchives = new HashSet<Archive>();
436                 for (Package p : getInstalledPackages(monitor.createSubMonitor(1))) {
437                     for (Archive a : p.getArchives()) {
438                         installedArchives.add(a);
439                     }
440                 }
441 
442                 int numInstalled = 0;
443                 nextArchive: for (ArchiveInfo ai : archives) {
444                     Archive archive = ai.getNewArchive();
445                     if (archive == null) {
446                         // This is not supposed to happen.
447                         continue nextArchive;
448                     }
449 
450                     int nextProgress = monitor.getProgress() + progressPerArchive;
451                     try {
452                         if (monitor.isCancelRequested()) {
453                             break;
454                         }
455 
456                         ArchiveInfo[] adeps = ai.getDependsOn();
457                         if (adeps != null) {
458                             for (ArchiveInfo adep : adeps) {
459                                 Archive na = adep.getNewArchive();
460                                 if (na == null) {
461                                     // This archive depends on a missing archive.
462                                     // We shouldn't get here.
463                                     // Skip it.
464                                     monitor.log("Skipping '%1$s'; it depends on a missing package.",
465                                             archive.getParentPackage().getShortDescription());
466                                     continue nextArchive;
467                                 } else if (!installedArchives.contains(na)) {
468                                     // This archive depends on another one that was not installed.
469                                     // We shouldn't get here.
470                                     // Skip it.
471                                     monitor.logError("Skipping '%1$s'; it depends on '%2$s' which was not installed.",
472                                             archive.getParentPackage().getShortDescription(),
473                                             adep.getShortDescription());
474                                     continue nextArchive;
475                                 }
476                             }
477                         }
478 
479                         if (!preInstallHookInvoked) {
480                             preInstallHookInvoked = true;
481                             broadcastPreInstallHook();
482                         }
483 
484                         ArchiveInstaller installer = createArchiveInstaler();
485                         if (installer.install(ai,
486                                               mOsSdkRoot,
487                                               forceHttp,
488                                               mSdkManager,
489                                               monitor)) {
490                             // We installed this archive.
491                             newlyInstalledArchives.add(archive);
492                             installedArchives.add(archive);
493                             numInstalled++;
494 
495                             // If this package was replacing an existing one, the old one
496                             // is no longer installed.
497                             installedArchives.remove(ai.getReplaced());
498 
499                             // Check if we successfully installed a platform-tool or add-on package.
500                             if (archive.getParentPackage() instanceof AddonPackage) {
501                                 installedAddon = true;
502                             } else if (archive.getParentPackage() instanceof ToolPackage) {
503                                 installedTools = true;
504                             } else if (archive.getParentPackage() instanceof PlatformToolPackage) {
505                                 installedPlatformTools = true;
506                             }
507                         }
508 
509                     } catch (Throwable t) {
510                         // Display anything unexpected in the monitor.
511                         String msg = t.getMessage();
512                         if (msg != null) {
513                             msg = String.format("Unexpected Error installing '%1$s': %2$s: %3$s",
514                                     archive.getParentPackage().getShortDescription(),
515                                     t.getClass().getCanonicalName(), msg);
516                         } else {
517                             // no error info? get the stack call to display it
518                             // At least that'll give us a better bug report.
519                             ByteArrayOutputStream baos = new ByteArrayOutputStream();
520                             t.printStackTrace(new PrintStream(baos));
521 
522                             msg = String.format("Unexpected Error installing '%1$s'\n%2$s",
523                                     archive.getParentPackage().getShortDescription(),
524                                     baos.toString());
525                         }
526 
527                         monitor.log(     "%1$s", msg);      //$NON-NLS-1$
528                         mSdkLog.error(t, "%1$s", msg);      //$NON-NLS-1$
529                     } finally {
530 
531                         // Always move the progress bar to the desired position.
532                         // This allows internal methods to not have to care in case
533                         // they abort early
534                         monitor.incProgress(nextProgress - monitor.getProgress());
535                     }
536                 }
537 
538                 if (installedAddon) {
539                     // Update the USB vendor ids for adb
540                     try {
541                         mSdkManager.updateAdb();
542                         monitor.log("Updated ADB to support the USB devices declared in the SDK add-ons.");
543                     } catch (Exception e) {
544                         mSdkLog.error(e, "Update ADB failed");
545                         monitor.logError("failed to update adb to support the USB devices declared in the SDK add-ons.");
546                     }
547                 }
548 
549                 if (preInstallHookInvoked) {
550                     broadcastPostInstallHook();
551                 }
552 
553                 if (installedAddon || installedPlatformTools) {
554                     // We need to restart ADB. Actually since we don't know if it's even
555                     // running, maybe we should just kill it and not start it.
556                     // Note: it turns out even under Windows we don't need to kill adb
557                     // before updating the tools folder, as adb.exe is (surprisingly) not
558                     // locked.
559 
560                     askForAdbRestart(monitor);
561                 }
562 
563                 if (installedTools) {
564                     notifyToolsNeedsToBeRestarted(flags);
565                 }
566 
567                 if (numInstalled == 0) {
568                     monitor.setDescription("Done. Nothing was installed.");
569                 } else {
570                     monitor.setDescription("Done. %1$d %2$s installed.",
571                             numInstalled,
572                             numInstalled == 1 ? "package" : "packages");
573 
574                     //notify listeners something was installed, so that they can refresh
575                     reloadSdk();
576                 }
577             }
578         });
579 
580         return newlyInstalledArchives;
581     }
582 
583     /**
584      * A comparator to sort all the {@link ArchiveInfo} based on their
585      * dependency level. This forces the installer to install first all packages
586      * with no dependency, then those with one level of dependency, etc.
587      */
588     private static class InstallOrderComparator implements Comparator<ArchiveInfo> {
589 
590         private final Map<ArchiveInfo, Integer> mOrders = new HashMap<ArchiveInfo, Integer>();
591 
compare(ArchiveInfo o1, ArchiveInfo o2)592         public int compare(ArchiveInfo o1, ArchiveInfo o2) {
593             int n1 = getDependencyOrder(o1);
594             int n2 = getDependencyOrder(o2);
595 
596             return n1 - n2;
597         }
598 
getDependencyOrder(ArchiveInfo ai)599         private int getDependencyOrder(ArchiveInfo ai) {
600             if (ai == null) {
601                 return 0;
602             }
603 
604             // reuse cached value, if any
605             Integer cached = mOrders.get(ai);
606             if (cached != null) {
607                 return cached.intValue();
608             }
609 
610             ArchiveInfo[] deps = ai.getDependsOn();
611             if (deps == null) {
612                 return 0;
613             }
614 
615             // compute dependencies, recursively
616             int n = deps.length;
617 
618             for (ArchiveInfo dep : deps) {
619                 n += getDependencyOrder(dep);
620             }
621 
622             // cache it
623             mOrders.put(ai, Integer.valueOf(n));
624 
625             return n;
626         }
627 
628     }
629 
630     /**
631      * Attempts to restart ADB.
632      * <p/>
633      * If the "ask before restart" setting is set (the default), prompt the user whether
634      * now is a good time to restart ADB.
635      *
636      * @param monitor
637      */
askForAdbRestart(ITaskMonitor monitor)638     private void askForAdbRestart(ITaskMonitor monitor) {
639         final boolean[] canRestart = new boolean[] { true };
640 
641         if (getWindowShell() != null && getSettingsController().getAskBeforeAdbRestart()) {
642             // need to ask for permission first
643             final Shell shell = getWindowShell();
644             if (shell != null && !shell.isDisposed()) {
645                 shell.getDisplay().syncExec(new Runnable() {
646                     public void run() {
647                         if (!shell.isDisposed()) {
648                             canRestart[0] = MessageDialog.openQuestion(shell,
649                                     "ADB Restart",
650                                     "A package that depends on ADB has been updated. \n" +
651                                     "Do you want to restart ADB now?");
652                         }
653                     }
654                 });
655             }
656         }
657 
658         if (canRestart[0]) {
659             AdbWrapper adb = new AdbWrapper(getOsSdkRoot(), monitor);
660             adb.stopAdb();
661             adb.startAdb();
662         }
663     }
664 
notifyToolsNeedsToBeRestarted(int flags)665     private void notifyToolsNeedsToBeRestarted(int flags) {
666 
667         String msg = null;
668         if ((flags & TOOLS_MSG_UPDATED_FROM_ADT) != 0) {
669             msg =
670             "The Android SDK and AVD Manager that you are currently using has been updated. " +
671             "Please also run Eclipse > Help > Check for Updates to see if the Android " +
672             "plug-in needs to be updated.";
673 
674         } else if ((flags & TOOLS_MSG_UPDATED_FROM_SDKMAN) != 0) {
675             msg =
676             "The Android SDK and AVD Manager that you are currently using has been updated. " +
677             "It is recommended that you now close the manager window and re-open it. " +
678             "If you use Eclipse, please run Help > Check for Updates to see if the Android " +
679             "plug-in needs to be updated.";
680         }
681 
682         final String msg2 = msg;
683 
684         final Shell shell = getWindowShell();
685         if (msg2 != null && shell != null && !shell.isDisposed()) {
686             shell.getDisplay().syncExec(new Runnable() {
687                 public void run() {
688                     if (!shell.isDisposed()) {
689                         MessageDialog.openInformation(shell,
690                                 "Android Tools Updated",
691                                 msg2);
692                     }
693                 }
694             });
695         }
696     }
697 
698 
699     /**
700      * Tries to update all the *existing* local packages.
701      * This version *requires* to be run with a GUI.
702      * <p/>
703      * There are two modes of operation:
704      * <ul>
705      * <li>If selectedArchives is null, refreshes all sources, compares the available remote
706      * packages with the current local ones and suggest updates to be done to the user (including
707      * new platforms that the users doesn't have yet).
708      * <li>If selectedArchives is not null, this represents a list of archives/packages that
709      * the user wants to install or update, so just process these.
710      * </ul>
711      *
712      * @param selectedArchives The list of remote archives to consider for the update.
713      *  This can be null, in which case a list of remote archive is fetched from all
714      *  available sources.
715      * @param includeObsoletes True if obsolete packages should be used when resolving what
716      *  to update.
717      * @param flags Optional flags for the installer, such as {@link #NO_TOOLS_MSG}.
718      * @return A list of archives that have been installed. Can be null if nothing was done.
719      */
updateOrInstallAll_WithGUI( Collection<Archive> selectedArchives, boolean includeObsoletes, int flags)720     public List<Archive> updateOrInstallAll_WithGUI(
721             Collection<Archive> selectedArchives,
722             boolean includeObsoletes,
723             int flags) {
724 
725         // Note: we no longer call refreshSources(true) here. This will be done
726         // automatically by computeUpdates() iif it needs to access sources to
727         // resolve missing dependencies.
728 
729         SdkUpdaterLogic ul = new SdkUpdaterLogic(this);
730         List<ArchiveInfo> archives = ul.computeUpdates(
731                 selectedArchives,
732                 getSources(),
733                 getLocalSdkParser().getPackages(),
734                 includeObsoletes);
735 
736         if (selectedArchives == null) {
737             loadRemoteAddonsList(new NullTaskMonitor(getSdkLog()));
738             ul.addNewPlatforms(
739                     archives,
740                     getSources(),
741                     getLocalSdkParser().getPackages(),
742                     includeObsoletes);
743         }
744 
745         // TODO if selectedArchives is null and archives.len==0, find if there are
746         // any new platform we can suggest to install instead.
747 
748         Collections.sort(archives);
749 
750         SdkUpdaterChooserDialog dialog =
751             new SdkUpdaterChooserDialog(getWindowShell(), this, archives);
752         dialog.open();
753 
754         ArrayList<ArchiveInfo> result = dialog.getResult();
755         if (result != null && result.size() > 0) {
756             return installArchives(result, flags);
757         }
758         return null;
759     }
760 
761     /**
762      * Fetches all archives available on the known remote sources.
763      *
764      * Used by {@link UpdaterData#listRemotePackages_NoGUI} and
765      * {@link UpdaterData#updateOrInstallAll_NoGUI}.
766      *
767      * @param includeObsoletes True to also list obsolete packages.
768      * @return A list of potential {@link ArchiveInfo} to install.
769      */
getRemoteArchives_NoGUI(boolean includeObsoletes)770     private List<ArchiveInfo> getRemoteArchives_NoGUI(boolean includeObsoletes) {
771         refreshSources(true);
772         loadRemoteAddonsList(new NullTaskMonitor(getSdkLog()));
773 
774         SdkUpdaterLogic ul = new SdkUpdaterLogic(this);
775         List<ArchiveInfo> archives = ul.computeUpdates(
776                 null /*selectedArchives*/,
777                 getSources(),
778                 getLocalSdkParser().getPackages(),
779                 includeObsoletes);
780 
781         ul.addNewPlatforms(
782                 archives,
783                 getSources(),
784                 getLocalSdkParser().getPackages(),
785                 includeObsoletes);
786 
787         Collections.sort(archives);
788         return archives;
789     }
790 
791     /**
792      * Lists remote packages available for install using
793      * {@link UpdaterData#updateOrInstallAll_NoGUI}.
794      *
795      * @param includeObsoletes True to also list obsolete packages.
796      * @param extendedOutput True to display more details on each package.
797      */
listRemotePackages_NoGUI(boolean includeObsoletes, boolean extendedOutput)798     public void listRemotePackages_NoGUI(boolean includeObsoletes, boolean extendedOutput) {
799 
800         List<ArchiveInfo> archives = getRemoteArchives_NoGUI(includeObsoletes);
801 
802         mSdkLog.printf("Packages available for installation or update: %1$d\n", archives.size());
803 
804         int index = 1;
805         for (ArchiveInfo ai : archives) {
806             Archive a = ai.getNewArchive();
807             if (a != null) {
808                 Package p = a.getParentPackage();
809                 if (p != null) {
810                     if (extendedOutput) {
811                         mSdkLog.printf("----------\n");
812                         mSdkLog.printf("id: %1$d or \"%2$s\"\n", index, p.installId());
813                         mSdkLog.printf("     Type: %1$s\n",
814                                 p.getClass().getSimpleName().replaceAll("Package", "")); //$NON-NLS-1$ //$NON-NLS-2$
815                         String desc = LineUtil.reformatLine("     Desc: %s\n",
816                                 p.getLongDescription());
817                         mSdkLog.printf("%s", desc); //$NON-NLS-1$
818                     } else {
819                         mSdkLog.printf("%1$ 4d- %2$s\n",
820                                 index,
821                                 p.getShortDescription());
822                     }
823                     index++;
824                 }
825             }
826         }
827     }
828 
829     /**
830      * Tries to update all the *existing* local packages.
831      * This version is intended to run without a GUI and
832      * only outputs to the current {@link ISdkLog}.
833      *
834      * @param pkgFilter A list of {@link SdkRepoConstants#NODES} or {@link Package#installId()}
835      *   or package indexes to limit the packages we can update or install.
836      *   A null or empty list means to update everything possible.
837      * @param includeObsoletes True to also list and install obsolete packages.
838      * @param dryMode True to check what would be updated/installed but do not actually
839      *   download or install anything.
840      * @return A list of archives that have been installed. Can be null if nothing was done.
841      */
updateOrInstallAll_NoGUI( Collection<String> pkgFilter, boolean includeObsoletes, boolean dryMode)842     public List<Archive> updateOrInstallAll_NoGUI(
843             Collection<String> pkgFilter,
844             boolean includeObsoletes,
845             boolean dryMode) {
846 
847         List<ArchiveInfo> archives = getRemoteArchives_NoGUI(includeObsoletes);
848 
849         // Filter the selected archives to only keep the ones matching the filter
850         if (pkgFilter != null && pkgFilter.size() > 0 && archives != null && archives.size() > 0) {
851             // Map filter types to an SdkRepository Package type,
852             // e.g. create a map "platform" => PlatformPackage.class
853             HashMap<String, Class<? extends Package>> pkgMap =
854                 new HashMap<String, Class<? extends Package>>();
855 
856             mapFilterToPackageClass(pkgMap, SdkRepoConstants.NODES);
857             mapFilterToPackageClass(pkgMap, SdkAddonConstants.NODES);
858 
859             // Prepare a map install-id => package instance
860             HashMap<String, Package> installIdMap = new HashMap<String, Package>();
861             for (ArchiveInfo ai : archives) {
862                 Archive a = ai.getNewArchive();
863                 if (a != null) {
864                     Package p = a.getParentPackage();
865                     if (p != null) {
866                         String id = p.installId();
867                         if (id != null && id.length() > 0 && !installIdMap.containsKey(id)) {
868                             installIdMap.put(id, p);
869                         }
870                     }
871                 }
872             }
873 
874             // Now intersect this with the pkgFilter requested by the user, in order to
875             // only keep the classes that the user wants to install.
876             // We also create a set with the package indices requested by the user
877             // and a set of install-ids requested by the user.
878 
879             HashSet<Class<? extends Package>> userFilteredClasses =
880                 new HashSet<Class<? extends Package>>();
881             SparseIntArray userFilteredIndices = new SparseIntArray();
882             Set<String> userFilteredInstallIds = new HashSet<String>();
883 
884             for (String type : pkgFilter) {
885                 if (installIdMap.containsKey(type)) {
886                     userFilteredInstallIds.add(type);
887 
888                 } else if (type.replaceAll("[0-9]+", "").length() == 0) {//$NON-NLS-1$ //$NON-NLS-2$
889                     // An all-digit number is a package index requested by the user.
890                     int index = Integer.parseInt(type);
891                     userFilteredIndices.put(index, index);
892 
893                 } else if (pkgMap.containsKey(type)) {
894                     userFilteredClasses.add(pkgMap.get(type));
895 
896                 } else {
897                     // This should not happen unless there's a mismatch in the package map.
898                     mSdkLog.error(null, "Ignoring unknown package filter '%1$s'", type);
899                 }
900             }
901 
902             // we don't need the maps anymore
903             pkgMap = null;
904             installIdMap = null;
905 
906             // Now filter the remote archives list to keep:
907             // - any package which class matches userFilteredClasses
908             // - any package index which matches userFilteredIndices
909             // - any package install id which matches userFilteredInstallIds
910 
911             int index = 1;
912             for (Iterator<ArchiveInfo> it = archives.iterator(); it.hasNext(); ) {
913                 boolean keep = false;
914                 ArchiveInfo ai = it.next();
915                 Archive a = ai.getNewArchive();
916                 if (a != null) {
917                     Package p = a.getParentPackage();
918                     if (p != null) {
919                         if (userFilteredInstallIds.contains(p.installId()) ||
920                                 userFilteredClasses.contains(p.getClass()) ||
921                                 userFilteredIndices.get(index) > 0) {
922                             keep = true;
923                         }
924 
925                         index++;
926                     }
927                 }
928 
929                 if (!keep) {
930                     it.remove();
931                 }
932             }
933 
934             if (archives.size() == 0) {
935                 mSdkLog.printf(LineUtil.reflowLine(
936                         "Warning: The package filter removed all packages. There is nothing to install.\nPlease consider trying to update again without a package filter.\n"));
937                 return null;
938             }
939         }
940 
941         if (archives != null && archives.size() > 0) {
942             if (dryMode) {
943                 mSdkLog.printf("Packages selected for install:\n");
944                 for (ArchiveInfo ai : archives) {
945                     Archive a = ai.getNewArchive();
946                     if (a != null) {
947                         Package p = a.getParentPackage();
948                         if (p != null) {
949                             mSdkLog.printf("- %1$s\n", p.getShortDescription());
950                         }
951                     }
952                 }
953                 mSdkLog.printf("\nDry mode is on so nothing is actually being installed.\n");
954             } else {
955                 return installArchives(archives, NO_TOOLS_MSG);
956             }
957         } else {
958             mSdkLog.printf("There is nothing to install or update.\n");
959         }
960 
961         return null;
962     }
963 
964     @SuppressWarnings("unchecked")
mapFilterToPackageClass( HashMap<String, Class<? extends Package>> inOutPkgMap, String[] nodes)965     private void mapFilterToPackageClass(
966             HashMap<String, Class<? extends Package>> inOutPkgMap,
967             String[] nodes) {
968 
969         // Automatically find the classes matching the node names
970         ClassLoader classLoader = getClass().getClassLoader();
971         String basePackage = Package.class.getPackage().getName();
972 
973         for (String node : nodes) {
974             // Capitalize the name
975             String name = node.substring(0, 1).toUpperCase() + node.substring(1);
976 
977             // We can have one dash at most in a name. If it's present, we'll try
978             // with the dash or with the next letter capitalized.
979             int dash = name.indexOf('-');
980             if (dash > 0) {
981                 name = name.replaceFirst("-", "");
982             }
983 
984             for (int alternatives = 0; alternatives < 2; alternatives++) {
985 
986                 String fqcn = basePackage + '.' + name + "Package";  //$NON-NLS-1$
987                 try {
988                     Class<? extends Package> clazz =
989                         (Class<? extends Package>) classLoader.loadClass(fqcn);
990                     if (clazz != null) {
991                         inOutPkgMap.put(node, clazz);
992                         continue;
993                     }
994                 } catch (Throwable ignore) {
995                 }
996 
997                 if (alternatives == 0 && dash > 0) {
998                     // Try an alternative where the next letter after the dash
999                     // is converted to an upper case.
1000                     name = name.substring(0, dash) +
1001                            name.substring(dash, dash + 1).toUpperCase() +
1002                            name.substring(dash + 1);
1003                 } else {
1004                     break;
1005                 }
1006             }
1007         }
1008     }
1009 
1010     /**
1011      * Refresh all sources. This is invoked either internally (reusing an existing monitor)
1012      * or as a UI callback on the remote page "Refresh" button (in which case the monitor is
1013      * null and a new task should be created.)
1014      *
1015      * @param forceFetching When true, load sources that haven't been loaded yet.
1016      *                      When false, only refresh sources that have been loaded yet.
1017      */
refreshSources(final boolean forceFetching)1018     public void refreshSources(final boolean forceFetching) {
1019         assert mTaskFactory != null;
1020 
1021         final boolean forceHttp = getSettingsController().getForceHttp();
1022 
1023         mTaskFactory.start("Refresh Sources", new ITask() {
1024             public void run(ITaskMonitor monitor) {
1025 
1026                 if (mStateFetchRemoteAddonsList <= 0) {
1027                     loadRemoteAddonsListInTask(monitor);
1028                 }
1029 
1030                 SdkSource[] sources = mSources.getAllSources();
1031                 monitor.setDescription("Refresh Sources");
1032                 monitor.setProgressMax(monitor.getProgress() + sources.length);
1033                 for (SdkSource source : sources) {
1034                     if (forceFetching ||
1035                             source.getPackages() != null ||
1036                             source.getFetchError() != null) {
1037                         source.load(monitor.createSubMonitor(1), forceHttp);
1038                     }
1039                     monitor.incProgress(1);
1040                 }
1041             }
1042         });
1043     }
1044 
1045     /**
1046      * Loads the remote add-ons list.
1047      */
loadRemoteAddonsList(ITaskMonitor monitor)1048     public void loadRemoteAddonsList(ITaskMonitor monitor) {
1049 
1050         if (mStateFetchRemoteAddonsList != 0) {
1051             return;
1052         }
1053 
1054         mTaskFactory.start("Load Add-ons List", monitor, new ITask() {
1055             public void run(ITaskMonitor subMonitor) {
1056                 loadRemoteAddonsListInTask(subMonitor);
1057             }
1058         });
1059     }
1060 
loadRemoteAddonsListInTask(ITaskMonitor monitor)1061     private void loadRemoteAddonsListInTask(ITaskMonitor monitor) {
1062         mStateFetchRemoteAddonsList = -1;
1063 
1064         // SDK_TEST_URLS is a semicolon-separated list of URLs that can be used to
1065         // seed the SDK Updater list. This is only meant as a debugging and QA testing
1066         // tool and not for user usage.
1067         //
1068         // To be used, the URLs must either end with the / or end with the canonical
1069         // filename expected for an addon list. This lets QA use URLs ending with /
1070         // to cover all cases.
1071         //
1072         // Since SDK_TEST_URLS can contain many such URLs, we take the first one that
1073         // matches our criteria.
1074         String url = System.getenv("SDK_TEST_URLS");    //$NON-NLS-1$
1075 
1076         if (url == null) {
1077             // No override, use the canonical URL.
1078             url = SdkAddonsListConstants.URL_ADDON_LIST;
1079         } else {
1080             String[] urls = url.split(";");             //$NON-NLS-1$
1081             url = null;
1082             for (String u : urls) {
1083                 u = u.trim();
1084                 // This is an URL that comes from the env var. We expect it to either
1085                 // end with a / or the canonical name, otherwise we don't use it.
1086                 if (u.endsWith("/")) {                  //$NON-NLS-1$
1087                     url = u + SdkAddonsListConstants.URL_DEFAULT_FILENAME;
1088                     break;
1089                 } else if (u.endsWith(SdkAddonsListConstants.URL_DEFAULT_FILENAME)) {
1090                     url = u;
1091                     break;
1092                 }
1093             }
1094         }
1095 
1096         if (url != null) {
1097             if (getSettingsController().getForceHttp()) {
1098                 url = url.replaceAll("https://", "http://");    //$NON-NLS-1$ //$NON-NLS-2$
1099             }
1100 
1101             AddonsListFetcher fetcher = new AddonsListFetcher();
1102             Site[] sites = fetcher.fetch(monitor, url);
1103             if (sites != null) {
1104                 mSources.removeAll(SdkSourceCategory.ADDONS_3RD_PARTY);
1105 
1106                 for (Site s : sites) {
1107                     mSources.add(SdkSourceCategory.ADDONS_3RD_PARTY,
1108                                  new SdkAddonSource(s.getUrl(), s.getUiName()));
1109                 }
1110 
1111                 mStateFetchRemoteAddonsList = 1;
1112             }
1113         }
1114 
1115         monitor.setDescription("Fetched Add-ons List successfully");
1116     }
1117 
1118     /**
1119      * Safely invoke all the registered {@link ISdkChangeListener#onSdkLoaded()}.
1120      * This can be called from any thread.
1121      */
broadcastOnSdkLoaded()1122     public void broadcastOnSdkLoaded() {
1123         if (mWindowShell != null && mListeners.size() > 0) {
1124             mWindowShell.getDisplay().syncExec(new Runnable() {
1125                 public void run() {
1126                     for (ISdkChangeListener listener : mListeners) {
1127                         try {
1128                             listener.onSdkLoaded();
1129                         } catch (Throwable t) {
1130                             mSdkLog.error(t, null);
1131                         }
1132                     }
1133                 }
1134             });
1135         }
1136     }
1137 
1138     /**
1139      * Safely invoke all the registered {@link ISdkChangeListener#onSdkReload()}.
1140      * This can be called from any thread.
1141      */
broadcastOnSdkReload()1142     private void broadcastOnSdkReload() {
1143         if (mWindowShell != null && mListeners.size() > 0) {
1144             mWindowShell.getDisplay().syncExec(new Runnable() {
1145                 public void run() {
1146                     for (ISdkChangeListener listener : mListeners) {
1147                         try {
1148                             listener.onSdkReload();
1149                         } catch (Throwable t) {
1150                             mSdkLog.error(t, null);
1151                         }
1152                     }
1153                 }
1154             });
1155         }
1156     }
1157 
1158     /**
1159      * Safely invoke all the registered {@link ISdkChangeListener#preInstallHook()}.
1160      * This can be called from any thread.
1161      */
broadcastPreInstallHook()1162     private void broadcastPreInstallHook() {
1163         if (mWindowShell != null && mListeners.size() > 0) {
1164             mWindowShell.getDisplay().syncExec(new Runnable() {
1165                 public void run() {
1166                     for (ISdkChangeListener listener : mListeners) {
1167                         try {
1168                             listener.preInstallHook();
1169                         } catch (Throwable t) {
1170                             mSdkLog.error(t, null);
1171                         }
1172                     }
1173                 }
1174             });
1175         }
1176     }
1177 
1178     /**
1179      * Safely invoke all the registered {@link ISdkChangeListener#postInstallHook()}.
1180      * This can be called from any thread.
1181      */
broadcastPostInstallHook()1182     private void broadcastPostInstallHook() {
1183         if (mWindowShell != null && mListeners.size() > 0) {
1184             mWindowShell.getDisplay().syncExec(new Runnable() {
1185                 public void run() {
1186                     for (ISdkChangeListener listener : mListeners) {
1187                         try {
1188                             listener.postInstallHook();
1189                         } catch (Throwable t) {
1190                             mSdkLog.error(t, null);
1191                         }
1192                     }
1193                 }
1194             });
1195         }
1196     }
1197 
1198     /**
1199      * Internal helper to return a new {@link ArchiveInstaller}.
1200      * This allows us to override the installer for unit-testing.
1201      */
1202     @VisibleForTesting(visibility=Visibility.PRIVATE)
createArchiveInstaler()1203     protected ArchiveInstaller createArchiveInstaler() {
1204         return new ArchiveInstaller();
1205     }
1206 
1207 }
1208