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