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