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