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.sdkman1; 18 19 20 import com.android.sdklib.ISdkLog; 21 import com.android.sdklib.SdkConstants; 22 import com.android.sdkuilib.internal.repository.IPageListener; 23 import com.android.sdkuilib.internal.repository.ISdkUpdaterWindow; 24 import com.android.sdkuilib.internal.repository.ISettingsPage; 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.tasks.ProgressTaskFactory; 31 import com.android.sdkuilib.repository.ISdkChangeListener; 32 import com.android.sdkuilib.repository.SdkUpdaterWindow.SdkInvocationContext; 33 import com.android.util.Pair; 34 35 import org.eclipse.swt.SWT; 36 import org.eclipse.swt.custom.SashForm; 37 import org.eclipse.swt.custom.StackLayout; 38 import org.eclipse.swt.events.DisposeEvent; 39 import org.eclipse.swt.events.DisposeListener; 40 import org.eclipse.swt.events.SelectionAdapter; 41 import org.eclipse.swt.events.SelectionEvent; 42 import org.eclipse.swt.graphics.Point; 43 import org.eclipse.swt.layout.GridData; 44 import org.eclipse.swt.layout.GridLayout; 45 import org.eclipse.swt.widgets.Composite; 46 import org.eclipse.swt.widgets.Display; 47 import org.eclipse.swt.widgets.List; 48 import org.eclipse.swt.widgets.Shell; 49 50 import java.util.ArrayList; 51 52 /** 53 * This is the private implementation of the UpdateWindow for the 54 * first version of the SDK Manager. 55 * <p/> 56 * This window has a sash, with a list of available pages on the left 57 * (AVD list, settings, about, installed packages, available packages) 58 * and the corresponding page on the right. 59 */ 60 public class SdkUpdaterWindowImpl1 implements ISdkUpdaterWindow { 61 62 private final Shell mParentShell; 63 /** Internal data shared between the window and its pages. */ 64 private final UpdaterData mUpdaterData; 65 /** The array of pages instances. Only one is visible at a time. */ 66 private ArrayList<Composite> mPages = new ArrayList<Composite>(); 67 /** Indicates a page change is due to an internal request. Prevents callbacks from looping. */ 68 private boolean mInternalPageChange; 69 /** A list of extra pages to instantiate. Each entry is an object array with 2 elements: 70 * the string title and the Composite class to instantiate to create the page. */ 71 private ArrayList<Pair<Class<? extends UpdaterPage>, Purpose>> mExtraPages; 72 /** A factory to create progress task dialogs. */ 73 private ProgressTaskFactory mTaskFactory; 74 /** The initial page to display. If null or not a know class, the first page will be displayed. 75 * Must be set before the first call to {@link #open()}. */ 76 private Class<? extends Composite> mInitialPage; 77 /** Sets whether the auto-update wizard will be shown when opening the window. */ 78 private boolean mRequestAutoUpdate; 79 80 // --- UI members --- 81 82 protected Shell mShell; 83 private List mPageList; 84 private Composite mPagesRootComposite; 85 private AvdManagerPage mAvdManagerPage; 86 private StackLayout mStackLayout; 87 private LocalPackagesPage mLocalPackagePage; 88 private RemotePackagesPage mRemotePackagesPage; 89 90 /** 91 * Creates a new window. Caller must call open(), which will block. 92 * 93 * @param parentShell Parent shell. 94 * @param sdkLog Logger. Cannot be null. 95 * @param osSdkRoot The OS path to the SDK root. 96 * @param context The {@link SdkInvocationContext} to change the behavior depending on who's 97 * opening the SDK Manager. Unused for SdkMan1. 98 */ 99 public SdkUpdaterWindowImpl1( 100 Shell parentShell, 101 ISdkLog sdkLog, 102 String osSdkRoot, 103 SdkInvocationContext context/*unused*/) { 104 mParentShell = parentShell; 105 mUpdaterData = new UpdaterData(osSdkRoot, sdkLog); 106 } 107 108 /** 109 * Opens the window. 110 * @wbp.parser.entryPoint 111 */ 112 public void open() { 113 if (mParentShell == null) { 114 Display.setAppName("Android"); //$hide$ (hide from SWT designer) 115 } 116 117 createShell(); 118 preCreateContent(); 119 createContents(); 120 mShell.open(); 121 mShell.layout(); 122 123 if (postCreateContent()) { //$hide$ (hide from SWT designer) 124 Display display = Display.getDefault(); 125 while (!mShell.isDisposed()) { 126 if (!display.readAndDispatch()) { 127 display.sleep(); 128 } 129 } 130 } 131 132 dispose(); //$hide$ 133 } 134 135 private void createShell() { 136 mShell = new Shell(mParentShell, SWT.SHELL_TRIM | SWT.APPLICATION_MODAL); 137 mShell.addDisposeListener(new DisposeListener() { 138 public void widgetDisposed(DisposeEvent e) { 139 onAndroidSdkUpdaterDispose(); //$hide$ (hide from SWT designer) 140 } 141 }); 142 143 GridLayout glShell = new GridLayout(2, false); 144 glShell.verticalSpacing = 0; 145 glShell.horizontalSpacing = 0; 146 glShell.marginWidth = 0; 147 glShell.marginHeight = 0; 148 mShell.setLayout(glShell); 149 150 mShell.setMinimumSize(new Point(500, 300)); 151 mShell.setSize(700, 500); 152 mShell.setText("Android SDK and AVD Manager"); 153 } 154 155 /** 156 * Create contents of the window. 157 */ 158 private void createContents() { 159 SashForm sashForm = new SashForm(mShell, SWT.NONE); 160 sashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); 161 162 mPageList = new List(sashForm, SWT.BORDER); 163 mPageList.addSelectionListener(new SelectionAdapter() { 164 @Override 165 public void widgetSelected(SelectionEvent e) { 166 onPageListSelected(); //$hide$ (hide from SWT designer) 167 } 168 }); 169 170 createPagesRoot(sashForm); 171 172 sashForm.setWeights(new int[] {150, 576}); 173 } 174 175 private void createPagesRoot(Composite parent) { 176 mPagesRootComposite = new Composite(parent, SWT.NONE); 177 mStackLayout = new StackLayout(); 178 mPagesRootComposite.setLayout(mStackLayout); 179 } 180 181 // -- Start of internal part ---------- 182 // Hide everything down-below from SWT designer 183 //$hide>>$ 184 185 // --- Public API ----------- 186 187 188 /** 189 * Registers an extra page for the updater window. 190 * <p/> 191 * Pages must derive from {@link Composite} and implement a constructor that takes 192 * a single parent {@link Composite} argument. 193 * <p/> 194 * All pages must be registered before the call to {@link #open()}. 195 * 196 * @param pageClass The {@link Composite}-derived class that will implement the page. 197 * @param purpose The purpose of this page, e.g. an about box, settings page or generic. 198 */ 199 @SuppressWarnings("unchecked") 200 public void registerPage(Class<? extends UpdaterPage> pageClass, 201 Purpose purpose) { 202 if (mExtraPages == null) { 203 mExtraPages = new ArrayList<Pair<Class<? extends UpdaterPage>, Purpose>>(); 204 } 205 Pair<?, Purpose> value = Pair.of(pageClass, purpose); 206 mExtraPages.add((Pair<Class<? extends UpdaterPage>, Purpose>) value); 207 } 208 209 /** 210 * Indicate the initial page that should be selected when the window opens. 211 * This must be called before the call to {@link #open()}. 212 * If null or if the page class is not found, the first page will be selected. 213 */ 214 public void setInitialPage(Class<? extends Composite> pageClass) { 215 mInitialPage = pageClass; 216 } 217 218 /** 219 * Sets whether the auto-update wizard will be shown when opening the window. 220 * <p/> 221 * This must be called before the call to {@link #open()}. 222 */ 223 public void setRequestAutoUpdate(boolean requestAutoUpdate) { 224 mRequestAutoUpdate = requestAutoUpdate; 225 } 226 227 /** 228 * Adds a new listener to be notified when a change is made to the content of the SDK. 229 */ 230 public void addListener(ISdkChangeListener listener) { 231 mUpdaterData.addListeners(listener); 232 } 233 234 /** 235 * Removes a new listener to be notified anymore when a change is made to the content of 236 * the SDK. 237 */ 238 public void removeListener(ISdkChangeListener listener) { 239 mUpdaterData.removeListener(listener); 240 } 241 242 // --- Internals & UI Callbacks ----------- 243 244 /** 245 * Called by {@link #postCreateContent()} to generate the pages that can be 246 * displayed in the window. 247 */ 248 protected void createPages() { 249 mAvdManagerPage = new AvdManagerPage(mPagesRootComposite, SWT.BORDER, mUpdaterData); 250 251 mLocalPackagePage = new LocalPackagesPage(mPagesRootComposite, SWT.BORDER, mUpdaterData); 252 mRemotePackagesPage = new RemotePackagesPage(mPagesRootComposite, SWT.BORDER, mUpdaterData); 253 254 addPage(mAvdManagerPage, "Virtual devices"); 255 256 addPage(mLocalPackagePage, "Installed packages"); 257 addPage(mRemotePackagesPage, "Available packages"); 258 259 addExtraPages(); 260 } 261 262 /** 263 * Callback called when the window shell is disposed. 264 */ 265 private void onAndroidSdkUpdaterDispose() { 266 if (mUpdaterData != null) { 267 ImageFactory imgFactory = mUpdaterData.getImageFactory(); 268 if (imgFactory != null) { 269 imgFactory.dispose(); 270 } 271 } 272 } 273 274 /** 275 * Creates the icon of the window shell. 276 */ 277 private void setWindowImage(Shell androidSdkUpdater) { 278 String imageName = "android_icon_16.png"; //$NON-NLS-1$ 279 if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_DARWIN) { 280 imageName = "android_icon_128.png"; 281 } 282 283 if (mUpdaterData != null) { 284 ImageFactory imgFactory = mUpdaterData.getImageFactory(); 285 if (imgFactory != null) { 286 mShell.setImage(imgFactory.getImageByName(imageName)); 287 } 288 } 289 } 290 291 /** 292 * Called before the UI is created. 293 */ 294 private void preCreateContent() { 295 mUpdaterData.setWindowShell(mShell); 296 mTaskFactory = new ProgressTaskFactory(mShell); 297 mUpdaterData.setTaskFactory(mTaskFactory); 298 mUpdaterData.setImageFactory(new ImageFactory(mShell.getDisplay())); 299 } 300 301 /** 302 * Once the UI has been created, initializes the content. 303 * This creates the pages, selects the first one, setup sources and scan for local folders. 304 * 305 * Returns true if we should show the window. 306 */ 307 private boolean postCreateContent() { 308 setWindowImage(mShell); 309 createPages(); 310 311 setupSources(); 312 initializeSettings(); 313 selectInitialPage(); 314 315 if (mUpdaterData.checkIfInitFailed()) { 316 return false; 317 } 318 319 mUpdaterData.broadcastOnSdkLoaded(); 320 321 if (mRequestAutoUpdate) { 322 mUpdaterData.updateOrInstallAll_WithGUI( 323 null /*selectedArchives*/, 324 false /* includeObsoletes */, 325 0 /* flags */); 326 } 327 328 return true; 329 } 330 331 /** 332 * Called by the main loop when the window has been disposed. 333 */ 334 private void dispose() { 335 mUpdaterData.getSources().saveUserAddons(mUpdaterData.getSdkLog()); 336 } 337 338 // --- page switching --- 339 340 /** 341 * Adds an instance of a page to the page list. 342 * <p/> 343 * Each page is a {@link Composite}. The title of the page is stored in the 344 * {@link Composite#getData()} field. 345 */ 346 protected void addPage(Composite page, String title) { 347 assert title != null; 348 if (title == null) { 349 title = "Unknown"; 350 } 351 page.setData(title); 352 mPages.add(page); 353 if (mPageList != null) { 354 mPageList.add(title); 355 } 356 } 357 358 /** 359 * Adds all extra pages. For each page, instantiates an instance of the {@link Composite} 360 * using the constructor that takes a single {@link Composite} argument and then adds it 361 * to the page list. 362 */ 363 protected void addExtraPages() { 364 if (mExtraPages == null) { 365 return; 366 } 367 368 for (Pair<Class<? extends UpdaterPage>, Purpose> extraPage : mExtraPages) { 369 Class<? extends UpdaterPage> clazz = extraPage.getFirst(); 370 UpdaterPage instance = UpdaterPage.newInstance( 371 clazz, 372 mPagesRootComposite, 373 SWT.BORDER, 374 mUpdaterData.getSdkLog()); 375 if (instance != null) { 376 addPage(instance, instance.getPageTitle()); 377 } 378 } 379 } 380 381 /** 382 * Callback invoked when an item is selected in the page list. 383 * If this is not an internal page change, displays the given page. 384 */ 385 private void onPageListSelected() { 386 if (mInternalPageChange == false && mPageList != null) { 387 int index = mPageList.getSelectionIndex(); 388 if (index >= 0) { 389 displayPage(index); 390 } 391 } 392 } 393 394 /** 395 * Displays the page at the given index. 396 * 397 * @param index An index between 0 and {@link #mPages}'s length - 1. 398 */ 399 private void displayPage(int index) { 400 Composite page = mPages.get(index); 401 if (page != null) { 402 mStackLayout.topControl = page; 403 mPagesRootComposite.layout(true); 404 405 if (!mInternalPageChange && mPageList != null) { 406 mInternalPageChange = true; 407 mPageList.setSelection(index); 408 mInternalPageChange = false; 409 } 410 411 if (page instanceof IPageListener) { 412 ((IPageListener) page).onPageSelected(); 413 } 414 } 415 } 416 417 /** 418 * Used to initialize the sources. 419 */ 420 private void setupSources() { 421 mUpdaterData.setupDefaultSources(); 422 } 423 424 /** 425 * Initializes settings. 426 * This must be called after addExtraPages(), which created a settings page. 427 * Iterate through all the pages to find the first (and supposedly unique) setting page, 428 * and use it to load and apply these settings. 429 */ 430 private void initializeSettings() { 431 SettingsController c = mUpdaterData.getSettingsController(); 432 c.loadSettings(); 433 c.applySettings(); 434 435 for (Object page : mPages) { 436 if (page instanceof ISettingsPage) { 437 ISettingsPage settingsPage = (ISettingsPage) page; 438 439 c.setSettingsPage(settingsPage); 440 break; 441 } 442 } 443 } 444 445 /** 446 * Select and show the initial page. 447 * This will be either the page which class matches {@link #mInitialPage} or the 448 * first one in the list. 449 */ 450 private void selectInitialPage() { 451 int pageIndex = 0; 452 int i = 0; 453 for (Composite p : mPages) { 454 if (p.getClass().equals(mInitialPage)) { 455 pageIndex = i; 456 break; 457 } 458 i++; 459 } 460 461 displayPage(pageIndex); 462 if (mPageList != null) { 463 mPageList.setSelection(pageIndex); 464 } 465 } 466 467 // End of hiding from SWT Designer 468 //$hide<<$ 469 } 470