Home | History | Annotate | Download | only in sdkman2
      1 /*
      2  * Copyright (C) 2011 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.sdkman2;
     18 
     19 
     20 import com.android.sdklib.ISdkLog;
     21 import com.android.sdklib.SdkConstants;
     22 import com.android.sdklib.internal.repository.ITaskFactory;
     23 import com.android.sdkuilib.internal.repository.ISettingsPage;
     24 import com.android.sdkuilib.internal.repository.MenuBarWrapper;
     25 import com.android.sdkuilib.internal.repository.SettingsController;
     26 import com.android.sdkuilib.internal.repository.UpdaterData;
     27 import com.android.sdkuilib.internal.repository.UpdaterPage;
     28 import com.android.sdkuilib.internal.repository.UpdaterPage.Purpose;
     29 import com.android.sdkuilib.internal.repository.icons.ImageFactory;
     30 import com.android.sdkuilib.internal.repository.sdkman1.AvdManagerPage;
     31 import com.android.sdkuilib.repository.ISdkChangeListener;
     32 import com.android.sdkuilib.repository.SdkUpdaterWindow;
     33 import com.android.sdkuilib.repository.AvdManagerWindow.AvdInvocationContext;
     34 import com.android.sdkuilib.ui.GridDataBuilder;
     35 import com.android.sdkuilib.ui.GridLayoutBuilder;
     36 import com.android.sdkuilib.ui.SwtBaseDialog;
     37 import com.android.util.Pair;
     38 
     39 import org.eclipse.swt.SWT;
     40 import org.eclipse.swt.events.DisposeEvent;
     41 import org.eclipse.swt.events.DisposeListener;
     42 import org.eclipse.swt.events.SelectionAdapter;
     43 import org.eclipse.swt.events.SelectionEvent;
     44 import org.eclipse.swt.graphics.Point;
     45 import org.eclipse.swt.layout.GridData;
     46 import org.eclipse.swt.layout.GridLayout;
     47 import org.eclipse.swt.widgets.Button;
     48 import org.eclipse.swt.widgets.Composite;
     49 import org.eclipse.swt.widgets.Display;
     50 import org.eclipse.swt.widgets.Label;
     51 import org.eclipse.swt.widgets.Menu;
     52 import org.eclipse.swt.widgets.MenuItem;
     53 import org.eclipse.swt.widgets.Shell;
     54 
     55 import java.util.ArrayList;
     56 
     57 /**
     58  * This is an intermediate version of the {@link AvdManagerPage}
     59  * wrapped in its own standalone window for use from the SDK Manager 2.
     60  */
     61 public class AvdManagerWindowImpl1 {
     62 
     63     private static final String APP_NAME = "Android Virtual Device Manager";
     64     private static final String APP_NAME_MAC_MENU = "AVD Manager";
     65     private static final String SIZE_POS_PREFIX = "avdman1"; //$NON-NLS-1$
     66 
     67     private final Shell mParentShell;
     68     private final AvdInvocationContext mContext;
     69     /** Internal data shared between the window and its pages. */
     70     private final UpdaterData mUpdaterData;
     71     /** A list of extra pages to instantiate. Each entry is an object array with 2 elements:
     72      *  the string title and the Composite class to instantiate to create the page. */
     73     private ArrayList<Pair<Class<? extends UpdaterPage>, Purpose>> mExtraPages;
     74     /** Sets whether the auto-update wizard will be shown when opening the window. */
     75     private boolean mRequestAutoUpdate;
     76 
     77     // --- UI members ---
     78 
     79     protected Shell mShell;
     80     private AvdManagerPage mAvdPage;
     81     private SettingsController mSettingsController;
     82 
     83     /**
     84      * Creates a new window. Caller must call open(), which will block.
     85      *
     86      * @param parentShell Parent shell.
     87      * @param sdkLog Logger. Cannot be null.
     88      * @param osSdkRoot The OS path to the SDK root.
     89      * @param context The {@link AvdInvocationContext} to change the behavior depending on who's
     90      *  opening the SDK Manager.
     91      */
     92     public AvdManagerWindowImpl1(
     93             Shell parentShell,
     94             ISdkLog sdkLog,
     95             String osSdkRoot,
     96             AvdInvocationContext context) {
     97         mParentShell = parentShell;
     98         mContext = context;
     99         mUpdaterData = new UpdaterData(osSdkRoot, sdkLog);
    100     }
    101 
    102     /**
    103      * Creates a new window. Caller must call open(), which will block.
    104      * <p/>
    105      * This is to be used when the window is opened from {@link SdkUpdaterWindowImpl2}
    106      * to share the same {@link UpdaterData} structure.
    107      *
    108      * @param parentShell Parent shell.
    109      * @param updaterData The parent's updater data.
    110      * @param context The {@link AvdInvocationContext} to change the behavior depending on who's
    111      *  opening the SDK Manager.
    112      */
    113     public AvdManagerWindowImpl1(
    114             Shell parentShell,
    115             UpdaterData updaterData,
    116             AvdInvocationContext context) {
    117         mParentShell = parentShell;
    118         mContext = context;
    119         mUpdaterData = updaterData;
    120     }
    121 
    122     /**
    123      * Opens the window.
    124      * @wbp.parser.entryPoint
    125      */
    126     public void open() {
    127         if (mParentShell == null) {
    128             Display.setAppName(APP_NAME); //$hide$ (hide from SWT designer)
    129         }
    130 
    131         createShell();
    132         preCreateContent();
    133         createContents();
    134         createMenuBar();
    135         mShell.open();
    136         mShell.layout();
    137 
    138         boolean ok = postCreateContent();
    139 
    140         if (ok && mContext == AvdInvocationContext.STANDALONE) {
    141             Display display = Display.getDefault();
    142             while (!mShell.isDisposed()) {
    143                 if (!display.readAndDispatch()) {
    144                     display.sleep();
    145                 }
    146             }
    147 
    148             dispose();  //$hide$
    149         }
    150     }
    151 
    152     private void createShell() {
    153         mShell = new Shell(mParentShell, SWT.SHELL_TRIM | SWT.APPLICATION_MODAL);
    154         mShell.addDisposeListener(new DisposeListener() {
    155             public void widgetDisposed(DisposeEvent e) {
    156                 ShellSizeAndPos.saveSizeAndPos(mShell, SIZE_POS_PREFIX);
    157 
    158                 if (mContext != AvdInvocationContext.SDK_MANAGER) {
    159                     // When invoked from the sdk manager, don't dispose
    160                     // resources that the sdk manager still needs.
    161                     onAndroidSdkUpdaterDispose();    //$hide$ (hide from SWT designer)
    162                 }
    163             }
    164         });
    165 
    166         GridLayout glShell = new GridLayout(2, false);
    167         glShell.verticalSpacing = 0;
    168         glShell.horizontalSpacing = 0;
    169         glShell.marginWidth = 0;
    170         glShell.marginHeight = 0;
    171         mShell.setLayout(glShell);
    172 
    173         mShell.setMinimumSize(new Point(500, 300));
    174         mShell.setSize(700, 500);
    175         mShell.setText(APP_NAME);
    176 
    177         ShellSizeAndPos.loadSizeAndPos(mShell, SIZE_POS_PREFIX);
    178     }
    179 
    180     private void createContents() {
    181 
    182         mAvdPage = new AvdManagerPage(mShell, SWT.NONE, mUpdaterData);
    183         mAvdPage.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1));
    184     }
    185 
    186     private void createMenuBar() {
    187 
    188         if (mContext != AvdInvocationContext.STANDALONE) {
    189             return;
    190         }
    191 
    192         Menu menuBar = new Menu(mShell, SWT.BAR);
    193         mShell.setMenuBar(menuBar);
    194 
    195         MenuItem menuBarTools = new MenuItem(menuBar, SWT.CASCADE);
    196         menuBarTools.setText("Tools");
    197 
    198         Menu menuTools = new Menu(menuBarTools);
    199         menuBarTools.setMenu(menuTools);
    200 
    201         MenuItem manageSdk = new MenuItem(menuTools, SWT.NONE);
    202         manageSdk.setText("Manage SDK...");
    203         manageSdk.addSelectionListener(new SelectionAdapter() {
    204             @Override
    205             public void widgetSelected(SelectionEvent event) {
    206                 onSdkManager();
    207             }
    208         });
    209 
    210         if (mContext != AvdInvocationContext.IDE) {
    211             // Note: when invoked from an IDE, the SwtMenuBar library isn't
    212             // available. This means this source should not directly import
    213             // any of SwtMenuBar classes, otherwise the whole window class
    214             // would fail to load. The MenuBarWrapper below helps to make
    215             // that indirection.
    216 
    217             try {
    218                 new MenuBarWrapper(APP_NAME_MAC_MENU, menuTools) {
    219                     @Override
    220                     public void onPreferencesMenuSelected() {
    221                         showRegisteredPage(Purpose.SETTINGS);
    222                     }
    223 
    224                     @Override
    225                     public void onAboutMenuSelected() {
    226                         showRegisteredPage(Purpose.ABOUT_BOX);
    227                     }
    228 
    229                     @Override
    230                     public void printError(String format, Object... args) {
    231                         if (mUpdaterData != null) {
    232                             mUpdaterData.getSdkLog().error(null, format, args);
    233                         }
    234                     }
    235                 };
    236             } catch (Exception e) {
    237                 mUpdaterData.getSdkLog().error(e, "Failed to setup menu bar");
    238                 e.printStackTrace();
    239             }
    240         }
    241     }
    242 
    243 
    244     // -- Start of internal part ----------
    245     // Hide everything down-below from SWT designer
    246     //$hide>>$
    247 
    248     // --- Public API -----------
    249 
    250 
    251     /**
    252      * Registers an extra page for the updater window.
    253      * <p/>
    254      * Pages must derive from {@link Composite} and implement a constructor that takes
    255      * a single parent {@link Composite} argument.
    256      * <p/>
    257      * All pages must be registered before the call to {@link #open()}.
    258      *
    259      * @param pageClass The {@link Composite}-derived class that will implement the page.
    260      * @param purpose The purpose of this page, e.g. an about box, settings page or generic.
    261      */
    262     @SuppressWarnings("unchecked")
    263     public void registerPage(Class<? extends UpdaterPage> pageClass,
    264             Purpose purpose) {
    265         if (mExtraPages == null) {
    266             mExtraPages = new ArrayList<Pair<Class<? extends UpdaterPage>, Purpose>>();
    267         }
    268         Pair<?, Purpose> value = Pair.of(pageClass, purpose);
    269         mExtraPages.add((Pair<Class<? extends UpdaterPage>, Purpose>) value);
    270     }
    271 
    272     /**
    273      * Indicate the initial page that should be selected when the window opens.
    274      * This must be called before the call to {@link #open()}.
    275      * If null or if the page class is not found, the first page will be selected.
    276      */
    277     public void setInitialPage(Class<? extends Composite> pageClass) {
    278         // Unused in this case. This window display only one page.
    279     }
    280 
    281     /**
    282      * Sets whether the auto-update wizard will be shown when opening the window.
    283      * <p/>
    284      * This must be called before the call to {@link #open()}.
    285      */
    286     public void setRequestAutoUpdate(boolean requestAutoUpdate) {
    287         mRequestAutoUpdate = requestAutoUpdate;
    288     }
    289 
    290     /**
    291      * Adds a new listener to be notified when a change is made to the content of the SDK.
    292      */
    293     public void addListener(ISdkChangeListener listener) {
    294         mUpdaterData.addListeners(listener);
    295     }
    296 
    297     /**
    298      * Removes a new listener to be notified anymore when a change is made to the content of
    299      * the SDK.
    300      */
    301     public void removeListener(ISdkChangeListener listener) {
    302         mUpdaterData.removeListener(listener);
    303     }
    304 
    305     // --- Internals & UI Callbacks -----------
    306 
    307     /**
    308      * Called before the UI is created.
    309      */
    310     private void preCreateContent() {
    311         mUpdaterData.setWindowShell(mShell);
    312         // We need the UI factory to create the UI
    313         mUpdaterData.setImageFactory(new ImageFactory(mShell.getDisplay()));
    314         // Note: we can't create the TaskFactory yet because we need the UI
    315         // to be created first, so this is done in postCreateContent().
    316     }
    317 
    318     /**
    319      * Once the UI has been created, initializes the content.
    320      * This creates the pages, selects the first one, setup sources and scan for local folders.
    321      *
    322      * Returns true if we should show the window.
    323      */
    324     private boolean postCreateContent() {
    325         setWindowImage(mShell);
    326 
    327         setupSources();
    328         initializeSettings();
    329 
    330         if (mUpdaterData.checkIfInitFailed()) {
    331             return false;
    332         }
    333 
    334         mUpdaterData.broadcastOnSdkLoaded();
    335 
    336         if (mRequestAutoUpdate) {
    337             mUpdaterData.updateOrInstallAll_WithGUI(
    338                     null /*selectedArchives*/,
    339                     false /* includeObsoletes */,
    340                     mContext == AvdInvocationContext.IDE ?
    341                             UpdaterData.TOOLS_MSG_UPDATED_FROM_ADT :
    342                                 UpdaterData.TOOLS_MSG_UPDATED_FROM_SDKMAN);
    343         }
    344 
    345         return true;
    346     }
    347 
    348     /**
    349      * Creates the icon of the window shell.
    350      *
    351      * @param shell The shell on which to put the icon
    352      */
    353     private void setWindowImage(Shell shell) {
    354         String imageName = "android_icon_16.png"; //$NON-NLS-1$
    355         if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_DARWIN) {
    356             imageName = "android_icon_128.png";
    357         }
    358 
    359         if (mUpdaterData != null) {
    360             ImageFactory imgFactory = mUpdaterData.getImageFactory();
    361             if (imgFactory != null) {
    362                 shell.setImage(imgFactory.getImageByName(imageName));
    363             }
    364         }
    365     }
    366 
    367     /**
    368      * Called by the main loop when the window has been disposed.
    369      */
    370     private void dispose() {
    371         mUpdaterData.getSources().saveUserAddons(mUpdaterData.getSdkLog());
    372     }
    373 
    374     /**
    375      * Callback called when the window shell is disposed.
    376      */
    377     private void onAndroidSdkUpdaterDispose() {
    378         if (mUpdaterData != null) {
    379             ImageFactory imgFactory = mUpdaterData.getImageFactory();
    380             if (imgFactory != null) {
    381                 imgFactory.dispose();
    382             }
    383         }
    384     }
    385 
    386     /**
    387      * Used to initialize the sources.
    388      */
    389     private void setupSources() {
    390         mUpdaterData.setupDefaultSources();
    391     }
    392 
    393     /**
    394      * Initializes settings.
    395      * This must be called after addExtraPages(), which created a settings page.
    396      * Iterate through all the pages to find the first (and supposedly unique) setting page,
    397      * and use it to load and apply these settings.
    398      */
    399     private void initializeSettings() {
    400         mSettingsController = mUpdaterData.getSettingsController();
    401         mSettingsController.loadSettings();
    402         mSettingsController.applySettings();
    403     }
    404 
    405     private void showRegisteredPage(Purpose purpose) {
    406         if (mExtraPages == null) {
    407             return;
    408         }
    409 
    410         Class<? extends UpdaterPage> clazz = null;
    411 
    412         for (Pair<Class<? extends UpdaterPage>, Purpose> extraPage : mExtraPages) {
    413             if (extraPage.getSecond() == purpose) {
    414                 clazz = extraPage.getFirst();
    415                 break;
    416             }
    417         }
    418 
    419         if (clazz != null) {
    420             PageDialog d = new PageDialog(mShell, clazz, purpose == Purpose.SETTINGS);
    421             d.open();
    422         }
    423     }
    424 
    425     private void onSdkManager() {
    426         ITaskFactory oldFactory = mUpdaterData.getTaskFactory();
    427 
    428         try {
    429             SdkUpdaterWindowImpl2 win = new SdkUpdaterWindowImpl2(
    430                     mShell,
    431                     mUpdaterData,
    432                     SdkUpdaterWindow.SdkInvocationContext.AVD_MANAGER);
    433 
    434             for (Pair<Class<? extends UpdaterPage>, Purpose> page : mExtraPages) {
    435                 win.registerPage(page.getFirst(), page.getSecond());
    436             }
    437 
    438             win.open();
    439         } catch (Exception e) {
    440             mUpdaterData.getSdkLog().error(e, "SDK Manager window error");
    441         } finally {
    442             mUpdaterData.setTaskFactory(oldFactory);
    443         }
    444     }
    445 
    446     // End of hiding from SWT Designer
    447     //$hide<<$
    448 
    449     // -----
    450 
    451     /**
    452      * Dialog used to display either the About page or the Settings (aka Options) page
    453      * with a "close" button.
    454      */
    455     private class PageDialog extends SwtBaseDialog {
    456 
    457         private final Class<? extends UpdaterPage> mPageClass;
    458         private final boolean mIsSettingsPage;
    459 
    460         protected PageDialog(
    461                 Shell parentShell,
    462                 Class<? extends UpdaterPage> pageClass,
    463                 boolean isSettingsPage) {
    464             super(parentShell, SWT.APPLICATION_MODAL, null /*title*/);
    465             mPageClass = pageClass;
    466             mIsSettingsPage = isSettingsPage;
    467         }
    468 
    469         @Override
    470         protected void createContents() {
    471             Shell shell = getShell();
    472             setWindowImage(shell);
    473 
    474             GridLayoutBuilder.create(shell).columns(2);
    475 
    476             UpdaterPage content = UpdaterPage.newInstance(
    477                     mPageClass,
    478                     shell,
    479                     SWT.NONE,
    480                     mUpdaterData.getSdkLog());
    481             GridDataBuilder.create(content).fill().grab().hSpan(2);
    482             if (content.getLayout() instanceof GridLayout) {
    483                 GridLayout gl = (GridLayout) content.getLayout();
    484                 gl.marginHeight = gl.marginWidth = 0;
    485             }
    486 
    487             if (mIsSettingsPage && content instanceof ISettingsPage) {
    488                 mSettingsController.setSettingsPage((ISettingsPage) content);
    489             }
    490 
    491             getShell().setText(
    492                     String.format("%1$s - %2$s", APP_NAME, content.getPageTitle()));
    493 
    494             Label filler = new Label(shell, SWT.NONE);
    495             GridDataBuilder.create(filler).hFill().hGrab();
    496 
    497             Button close = new Button(shell, SWT.PUSH);
    498             close.setText("Close");
    499             GridDataBuilder.create(close);
    500             close.addSelectionListener(new SelectionAdapter() {
    501                 @Override
    502                 public void widgetSelected(SelectionEvent e) {
    503                     close();
    504                 }
    505             });
    506         }
    507 
    508         @Override
    509         protected void postCreate() {
    510             // pass
    511         }
    512 
    513         @Override
    514         protected void close() {
    515             if (mIsSettingsPage) {
    516                 mSettingsController.setSettingsPage(null);
    517             }
    518             super.close();
    519         }
    520     }
    521 }
    522