/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.motorola.studio.android.packaging.ui.export; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.security.UnrecoverableKeyException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.jar.JarFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.core.runtime.jobs.IJobChangeEvent; import org.eclipse.core.runtime.jobs.IJobChangeListener; import org.eclipse.core.runtime.jobs.JobChangeAdapter; import org.eclipse.jdt.internal.core.JavaProject; import org.eclipse.jdt.internal.ui.JavaPluginImages; import org.eclipse.jface.dialogs.ErrorDialog; import org.eclipse.jface.dialogs.IMessageProvider; import org.eclipse.jface.dialogs.ProgressMonitorDialog; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.DecorationOverlayIcon; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.window.Window; import org.eclipse.jface.wizard.IWizardPage; import org.eclipse.jface.wizard.WizardDialog; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.DirectoryDialog; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Group; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.MessageBox; import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.plugin.AbstractUIPlugin; import com.motorola.studio.android.AndroidPlugin; import com.motorola.studio.android.adt.AdtUtils; import com.motorola.studio.android.adt.SdkUtils; import com.motorola.studio.android.common.log.StudioLogger; import com.motorola.studio.android.common.utilities.EclipseUtils; import com.motorola.studio.android.packaging.ui.PackagingUIPlugin; import com.motorola.studio.android.packaging.ui.i18n.Messages; import com.motorolamobility.studio.android.certmanager.CertificateManagerActivator; import com.motorolamobility.studio.android.certmanager.core.KeyStoreManager; import com.motorolamobility.studio.android.certmanager.exception.InvalidPasswordException; import com.motorolamobility.studio.android.certmanager.exception.KeyStoreManagerException; import com.motorolamobility.studio.android.certmanager.job.CreateKeyJob; import com.motorolamobility.studio.android.certmanager.packaging.PackageFile; import com.motorolamobility.studio.android.certmanager.packaging.sign.PackageFileSigner; import com.motorolamobility.studio.android.certmanager.packaging.sign.SignException; import com.motorolamobility.studio.android.certmanager.ui.model.IKeyStore; import com.motorolamobility.studio.android.certmanager.ui.model.ITreeNode; import com.motorolamobility.studio.android.certmanager.ui.wizards.CreateKeyWizard; import com.motorolamobility.studio.android.certmanager.ui.wizards.CreateKeystoreWizard; import com.motorolamobility.studio.android.certmanager.ui.wizards.SelectExistentKeystoreWizard; /** * * This Class is an area implementation for Package Export Wizards It contains * all UI and finish logics * */ @SuppressWarnings("restriction") public class PackageExportWizardArea { /** * It holds the selection */ private final IStructuredSelection selection; /** * The destination Folder selected by the user */ private Text destinationText; private Button selectAllButton; private Button deselectAllButton; private Button packageDestinationBrowseButton; private Button defaultDestination; private Button signCheckBox; private final Composite parentComposite; private final boolean signingEnabled; private final HashMap<IProject, Integer> projectSeverity; /** * The tree which contains all descriptor files */ private Tree tree; private String message; private int severity; private boolean treeSelectionChanged; private final Image icon_ok, icon_nok; private Combo keystores; private Combo keysCombo; private Group signingGroup; private Button buttonAddKey; // Used in parallel with keystore combo. Use it with the selection index. private static ArrayList<IKeyStore> keystoreList = new ArrayList<IKeyStore>(); //maps keystore->password private final Map<IKeyStore, String> keystorePasswords = new HashMap<IKeyStore, String>(); private IKeyStore previousSelectedKeystore; private String previousSelectedKey; private Button buttonExisting; private Button buttonAddNew; /** * Creates a new Export Area. * * @param selection * The current Selection */ public PackageExportWizardArea(IStructuredSelection selection, Composite parent, boolean signingEnabled) { this.selection = normalizeSelection(selection); this.parentComposite = parent; this.signingEnabled = signingEnabled; this.projectSeverity = new HashMap<IProject, Integer>(); validateProjects(); ImageDescriptor adtProjectImageDescriptor = AbstractUIPlugin.imageDescriptorFromPlugin("com.android.ide.eclipse.adt", //$NON-NLS-1$ "icons/android_project.png"); //$NON-NLS-1$ ImageDescriptor errorImageDescriptor = JavaPluginImages.DESC_OVR_ERROR; ImageDescriptor projectImage = PlatformUI.getWorkbench().getSharedImages() .getImageDescriptor(org.eclipse.ui.ide.IDE.SharedImages.IMG_OBJ_PROJECT); // (IDecoration.TOP_LEFT, IDecoration.TOP_RIGHT, // IDecoration.BOTTOM_LEFT, IDecoration.BOTTOM_RIGHT and // IDecoration.UNDERLAY). ImageDescriptor[] overlays = new ImageDescriptor[5]; overlays[1] = adtProjectImageDescriptor; icon_ok = new DecorationOverlayIcon(projectImage.createImage(), overlays).createImage(); overlays[2] = errorImageDescriptor; icon_nok = new DecorationOverlayIcon(projectImage.createImage(), overlays).createImage(); createControl(parent); this.treeSelectionChanged = false; } /** * It opens the Directory Selection Dialog that allows the user enter the * destination folder * * @param originalPath * The Folder to show first * * @return The entire path of the user choice */ private String directoryDialog(String originalPath) { DirectoryDialog directoryDialog; directoryDialog = new DirectoryDialog(parentComposite.getShell()); File directory = new File(originalPath); if (directory.exists()) { directoryDialog.setFilterPath(directory.getPath()); } String returnedPath = directoryDialog.open(); return returnedPath; } /** * This method normalize the selection only to contain projects and * descriptors * * @return * @throws CoreException */ @SuppressWarnings("unchecked") private IStructuredSelection normalizeSelection(IStructuredSelection selection) { ArrayList<Object> normalized = new ArrayList<Object>(); Iterator<Object> iterator = selection.iterator(); while (iterator.hasNext()) { Object item = iterator.next(); IResource resource = null; if (item instanceof IResource) { resource = (IResource) item; } else if (item instanceof IAdaptable) { try { resource = (IResource) ((IAdaptable) item).getAdapter(IResource.class); } catch (Exception e) { StudioLogger.warn("Error retrieving projects."); } } if (resource != null) { IProject project = resource.getProject(); if (!normalized.contains(project)) { normalized.add(project); } } } return new StructuredSelection(normalized); } /** * This method is responsible to add all the existent descriptor files of * the current workspace in the tree shown in the wizard. */ private void populateTree() { tree.removeAll(); IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects(); try { for (IProject project : projects) { if (project.isOpen() && (project.getNature(AndroidPlugin.Android_Nature) != null) && !SdkUtils.isLibraryProject(project)) { TreeItem item = new TreeItem(tree, SWT.NONE); item.setData(project); item.setText(project.getName()); item.setImage(projectSeverity.get(project) == IMessageProvider.ERROR ? icon_nok : icon_ok); if (selection.toList().contains(project)) { item.setChecked(true); } } } } catch (CoreException e) { StudioLogger.error(PackageExportWizardArea.class, "Error populating tree: " + e.getMessage()); //$NON-NLS-1$ } } /** * Create the destination selection group * * @param mainComposite * : the parent composite */ private void createDestinationGroup(Composite mainComposite) { // create destination group Group destinationGroup = new Group(mainComposite, SWT.SHADOW_ETCHED_OUT); GridLayout layout = new GridLayout(3, false); destinationGroup.setLayout(layout); GridData defaultDestGridData = new GridData(SWT.FILL, SWT.CENTER, true, false, 3, 1); destinationGroup.setLayoutData(defaultDestGridData); destinationGroup.setText(Messages.PACKAGE_EXPORT_WIZARD_AREA_DESTINATION_LABEL); // Default Destination this.defaultDestination = new Button(destinationGroup, SWT.CHECK); defaultDestGridData = new GridData(SWT.FILL, SWT.FILL, true, false, 3, 1); this.defaultDestination.setLayoutData(defaultDestGridData); this.defaultDestination.addListener(SWT.Selection, new DefaultDestinationListener()); this.defaultDestination.setText(Messages.PACKAGE_EXPORT_WIZARD_USE_DEFAULT_DESTINATION); this.defaultDestination.setSelection(true); // Package Destination Label Label packageDestinationLabel = new Label(destinationGroup, SWT.NONE); packageDestinationLabel.setText(Messages.PACKAGE_EXPORT_WIZARD_PACKAGE_DESTINATION_LABEL); GridData folderGridData = new GridData(SWT.LEFT, SWT.CENTER, false, false); packageDestinationLabel.setLayoutData(folderGridData); // Package Destination this.destinationText = new Text(destinationGroup, SWT.SINGLE | SWT.BORDER); folderGridData = new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1); this.destinationText.setLayoutData(folderGridData); this.destinationText.setEnabled(!this.defaultDestination.getSelection()); this.destinationText.addListener(SWT.Modify, new DestinationTextListener()); // Browse Button this.packageDestinationBrowseButton = new Button(destinationGroup, SWT.PUSH); folderGridData = new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1); this.packageDestinationBrowseButton.setLayoutData(folderGridData); this.packageDestinationBrowseButton .setText(Messages.PACKAGE_EXPORT_WIZARD_AREA_BROWSE_BUTTON_LABEL); this.packageDestinationBrowseButton.setEnabled(!this.defaultDestination.getSelection()); this.packageDestinationBrowseButton.addListener(SWT.Selection, new PackageDestinationButtonListener()); } public final String[] getKeys(IKeyStore iKeyStore) throws KeyStoreManagerException, InvalidPasswordException { List<String> aliases = new ArrayList<String>(); aliases = iKeyStore.getAliases(getKeyStorePassword(iKeyStore)); return aliases.toArray(new String[0]); } public String openNewKeyWizard(IKeyStore keyStore, IJobChangeListener createKeyJobListener) { CreateKeyWizard wizard = new CreateKeyWizard(keyStore, getKeyStorePassword(getSelectedKeyStore()), createKeyJobListener); WizardDialog dialog = new WizardDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), wizard); dialog.open(); return wizard.getAlias(); } private void selectKeystore(IKeyStore newKeystore) { if (newKeystore != null) { int index = keystoreList.indexOf(newKeystore); if (keystores.getItemCount() > index) { keystores.select(index); loadKeys(newKeystore); setEnablement(true); } } else { keystores.deselectAll(); } previousSelectedKeystore = getSelectedKeyStore(); } private void selectKeystoreWithoutLoadingKeys(IKeyStore newKeystore) { if (newKeystore != null) { int index = keystoreList.indexOf(newKeystore); keysCombo.removeAll(); if (keystores.getItemCount() > index) { keystores.select(index); setEnablement(true); } } else { keystores.deselectAll(); } previousSelectedKeystore = getSelectedKeyStore(); } private boolean loadKeys(IKeyStore newKeystore) { boolean successfullyLoaded = true; keysCombo.removeAll(); try { String[] keys = getKeys(newKeystore); if (keys != null) { keysCombo.setItems(keys); } } catch (Exception e) { successfullyLoaded = false; StudioLogger.info(PackageExportWizardArea.class, NLS.bind("Could not load keys for keystore: {0}", newKeystore.getFile() //$NON-NLS-1$ .getAbsolutePath())); } selectKey(0); return successfullyLoaded; } private void selectKey(String key) { String[] keys = keysCombo.getItems(); int index = -1; int i = 0; for (String k : keys) { if (k.equals(key)) { index = i; break; } i++; } selectKey(index); } private void selectKey(int index) { if ((index >= 0) && (index < keysCombo.getItemCount())) { keysCombo.select(index); setEnablement(true); } else { keysCombo.deselectAll(); } } private void restorePreviousSelections() { selectKeystore(previousSelectedKeystore); selectKey(previousSelectedKey); } private String getSelectedKey() { String result = null; if (keysCombo.getSelectionIndex() >= 0) { result = keysCombo.getText(); } return result; } protected IKeyStore getSelectedKeyStore() { IKeyStore result = null; if (keystores.getSelectionIndex() >= 0) { result = keystoreList.get(keystores.getSelectionIndex()); } return result; } /** * Create the sign selection group * * @param mainComposite: the parent composite */ private void createSignGroup(Composite mainComposite) { // Create signing group signingGroup = new Group(mainComposite, SWT.SHADOW_ETCHED_OUT); GridLayout layout = new GridLayout(4, false); GridData layoutData = new GridData(SWT.FILL, SWT.CENTER, true, false, 4, 1); signingGroup.setLayout(layout); signingGroup.setLayoutData(layoutData); signingGroup.setText(Messages.PACKAGE_EXPORT_WIZARD_AREA_SIGNING_TAB_TEXT); // Sign button/Check box this.signCheckBox = new Button(signingGroup, SWT.CHECK); layoutData = new GridData(SWT.LEFT, SWT.CENTER, true, false, 4, 1); this.signCheckBox.setLayoutData(layoutData); this.signCheckBox.setText(Messages.PACKAGE_EXPORT_WIZARD_AREA_SIGN_CHECK_LABEL); this.signCheckBox.addListener(SWT.Selection, new SignButtonListener()); this.signCheckBox.setSelection(true); //-------------- // Keystore label Label keystoreLabel = new Label(signingGroup, SWT.NONE); keystoreLabel.setText(Messages.PACKAGE_EXPORT_WIZARD_AREA_SIGN_KEYSTORE_LABEL); GridData gridData = new GridData(SWT.BEGINNING, SWT.CENTER, false, false, 1, 1); keystoreLabel.setLayoutData(gridData); // Keystore combo this.keystores = new Combo(signingGroup, SWT.DROP_DOWN | SWT.READ_ONLY); gridData = new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1); gridData.widthHint = 250; this.keystores.setLayoutData(gridData); //populate mapped keystores from view populateKeystoresFromView(); keystores.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { IKeyStore selectedKeystore = keystoreList.get(keystores.getSelectionIndex()); boolean keysLoaded = loadKeys(selectedKeystore); if (!keysLoaded) { ITreeNode keystoreNode = (ITreeNode) selectedKeystore; if (keystoreNode.getNodeStatus().getCode() == IKeyStore.WRONG_KEYSTORE_TYPE_ERROR_CODE) { EclipseUtils.showInformationDialog( Messages.PackageExportWizardArea_WrongKeystoreTypeDialogTitle, NLS.bind( Messages.PackageExportWizardArea_WrongKeystoreTypeDialogMessage, keystoreNode.getName())); } restorePreviousSelections(); } else { previousSelectedKeystore = getSelectedKeyStore(); previousSelectedKey = getSelectedKey(); } setEnablement(true); } }); if (keystores.getItemCount() <= 0) { signCheckBox.setSelection(false); } // Add keystore buttons buttonExisting = new Button(signingGroup, SWT.NONE); buttonAddNew = new Button(signingGroup, SWT.NONE); gridData = new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1); buttonExisting.setLayoutData(gridData); gridData = new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1); buttonAddNew.setLayoutData(gridData); buttonExisting.setText(Messages.PackageExportWizardArea_MenuItem_UseExistent); buttonExisting.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { openSelectKeystoreWizard(); } }); buttonAddNew.setText(Messages.PackageExportWizardArea_MenuItem_AddNew); buttonAddNew.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { CreateKeystoreWizard createKeystoreWizard = new CreateKeystoreWizard(new CreateKeystoreJobListener()); WizardDialog dialog = new WizardDialog(parentComposite.getShell(), createKeystoreWizard); //open the wizard to create keystores dialog.create(); if (dialog.open() == Window.OK) { //user really created keystore String keystorePassword = createKeystoreWizard.getCreatedKeystorePassword(); addKeystore(createKeystoreWizard.getCreatedKeystoreNode(), true, keystorePassword); //required for case when just keystore is created, but no key is created //DO NOT call selectKeystore here because it has loadKeys, that may conflict with createKey (if a key is created), for this case the CreateKeystoreJobListener will solve the problem. selectKeystoreWithoutLoadingKeys(createKeystoreWizard.getCreatedKeystoreNode()); } } }); // Key label Label keyLabel = new Label(signingGroup, SWT.NONE); keyLabel.setText(Messages.PACKAGE_EXPORT_WIZARD_AREA_SIGN_KEY_LABEL); gridData = new GridData(SWT.BEGINNING, SWT.CENTER, false, false, 1, 1); keyLabel.setLayoutData(gridData); // Key Combo this.keysCombo = new Combo(signingGroup, SWT.DROP_DOWN | SWT.READ_ONLY); gridData = new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1); this.keysCombo.setLayoutData(gridData); if (keysCombo.getItemCount() <= 0) { signCheckBox.setSelection(false); } keysCombo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { super.widgetSelected(e); previousSelectedKey = getSelectedKey(); parentComposite.notifyListeners(SWT.Modify, new Event()); } }); // Add keystore button buttonAddKey = new Button(signingGroup, SWT.PUSH); buttonAddKey.setText(Messages.PackageExportWizardArea_AddKeyButton_Text); gridData = new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1); buttonAddKey.setLayoutData(gridData); buttonAddKey.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { IKeyStore keyStore = null; if (keystores.getSelectionIndex() >= 0) { keyStore = PackageExportWizardArea.keystoreList.get(keystores.getSelectionIndex()); } if (keyStore != null) { openNewKeyWizard(keyStore, new CreateKeyJobListener()); } setEnablement(true); } }); setEnablement(false); } /** * This class is required because when creating a new key during export, the threads are not synchronized (createKey and loadKeys). * Otherwise, wizard page could not finish accordingly. */ private class CreateKeyJobListener extends JobChangeAdapter { /* (non-Javadoc) * @see org.eclipse.core.runtime.jobs.JobChangeAdapter#done(org.eclipse.core.runtime.jobs.IJobChangeEvent) */ @Override public void done(final IJobChangeEvent event) { PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() { @Override public void run() { IKeyStore keyStore = ((CreateKeyJob) event.getJob()).getKeyStore(); String key = ((CreateKeyJob) event.getJob()).getCreatedKeyAlias(); loadKeys(keyStore); selectKey(key); } }); } } /** * This class is required because when creating a new key and new keystore during export, the threads are not synchronized (createKey and loadKeys). * Otherwise, wizard page could not finish accordingly. */ private class CreateKeystoreJobListener extends JobChangeAdapter { /* (non-Javadoc) * @see org.eclipse.core.runtime.jobs.JobChangeAdapter#done(org.eclipse.core.runtime.jobs.IJobChangeEvent) */ @Override public void done(final IJobChangeEvent event) { PlatformUI.getWorkbench().getDisplay().syncExec(new Runnable() { @Override public void run() { IKeyStore keyStore = ((CreateKeyJob) event.getJob()).getKeyStore(); String key = ((CreateKeyJob) event.getJob()).getCreatedKeyAlias(); loadKeys(keyStore); selectKeystore(keyStore); selectKey(key); } }); } } /** * Inserts keystores mapped into keystore combo */ protected void populateKeystoresFromView() { try { //when reopening wizard, we need to clear the list if (!keystoreList.isEmpty()) { keystoreList.clear(); keystores.removeAll(); } if ((KeyStoreManager.getInstance() != null) && (KeyStoreManager.getInstance().getKeyStores() != null)) { List<IKeyStore> keyStores = KeyStoreManager.getInstance().getKeyStores(); if (keyStores != null) { for (IKeyStore keyStore : keyStores) { insertKeystoreIntoCombo(keyStore); } } } } catch (KeyStoreManagerException e) { StudioLogger.error(PackageExportWizardArea.class, "Error retrieving keystore list", //$NON-NLS-1$ e); } } protected void insertKeystoreIntoCombo(IKeyStore iKeyStore) { File ksFile = iKeyStore.getFile(); keystores.add(ksFile.getName() + " - ( " + ksFile.getPath() + " )"); //$NON-NLS-1$ //$NON-NLS-2$ keystoreList.add(iKeyStore); } /** * Adds keystore to keystores combo box and model from GUI * @param iKeyStore * @param canSavePassword true if create/select and need to import into view, false if selecting and does NOT need to import into the view * @param password to retrieve keys */ protected void addKeystore(IKeyStore iKeyStore, boolean canSavePassword, String password) { keystorePasswords.put(iKeyStore, password); insertKeystoreIntoCombo(iKeyStore); } /** * Update the Default Destination The behavior is: if only one project is * selected, then default destination text is set to the folder of this * project if have more than one project selected or none than default * destination text is set to workspace root */ private void updateDefaultDestination() { ArrayList<IProject> selectedProjects = getSelectedProjects(); if (this.defaultDestination.getSelection()) { if ((selectedProjects != null) && (selectedProjects.size() == 1)) { this.destinationText.setText(selectedProjects.get(0).getLocation().toOSString()); } else { this.destinationText.setText(ResourcesPlugin.getWorkspace().getRoot().getLocation() .toOSString()); } } } /** * Get selected projects from the tree * * @return */ private ArrayList<IProject> getSelectedProjects() { ArrayList<IProject> projects = new ArrayList<IProject>(); for (TreeItem item : tree.getItems()) { if (item.getChecked()) { projects.add((IProject) item.getData()); } } return projects; } /** * Get selected items from the tree * * @return */ private ArrayList<TreeItem> getSelectedItems() { ArrayList<TreeItem> items = new ArrayList<TreeItem>(); for (TreeItem item : tree.getItems()) { if (item.getChecked()) { items.add(item); } } return items; } /** * This method creates the entire structure of the wizard page. Also, it * stores the user input. * * @param parent * Composite for all the elements. */ public void createControl(Composite parent) { Composite mainComposite = new Composite(parent, SWT.NONE); GridLayout gridLayout = new GridLayout(3, false); GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true, 3, 1); mainComposite.setLayout(gridLayout); mainComposite.setLayoutData(gridData); // Tree structure this.tree = new Tree(mainComposite, SWT.CHECK | SWT.MULTI | SWT.V_SCROLL | SWT.BORDER); gridData = new GridData(SWT.FILL, SWT.FILL, true, true, 2, 2); gridData.heightHint = 150; this.tree.setLayoutData(gridData); this.tree.addListener(SWT.Selection, new TreeListener()); // Add all the descriptor files to the tree populateTree(); // Select All Button this.selectAllButton = new Button(mainComposite, SWT.PUSH); gridData = new GridData(SWT.FILL, SWT.UP, false, false, 1, 1); this.selectAllButton.setLayoutData(gridData); this.selectAllButton.setText(Messages.PACKAGE_EXPORT_WIZARD_AREA_SELECT_ALL_BUTTON); this.selectAllButton.addListener(SWT.Selection, new TreeSelectionButtonListener(true)); // Deselect All button this.deselectAllButton = new Button(mainComposite, SWT.PUSH); gridData = new GridData(SWT.FILL, SWT.UP, false, false, 1, 1); this.deselectAllButton.setLayoutData(gridData); this.deselectAllButton.setText(Messages.PACKAGE_EXPORT_WIZARD_AREA_DESELECT_ALL_BUTTON); this.deselectAllButton.addListener(SWT.Selection, new TreeSelectionButtonListener(false)); createDestinationGroup(mainComposite); if (this.signingEnabled) { createSignGroup(mainComposite); } String path = ResourcesPlugin.getWorkspace().getRoot().getLocation().toOSString(); Object prj = this.selection.getFirstElement(); if ((prj != null) && (this.selection.size() == 1)) { if (prj instanceof IProject) { String realPath = ((IProject) prj).getLocation().toOSString(); this.destinationText.setText(realPath); } else if (prj instanceof IResource) { String realPath = path + ((IResource) prj).getProject().getFullPath().toOSString(); this.destinationText.setText(realPath); } } else { this.destinationText.setText(path); } /** * Force the focus to parent. This action make help ok */ if (!parent.isFocusControl()) { parent.forceFocus(); } } /** * Get all projects severities to avoid user selects erroneous projects */ private void validateProjects() { try { for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) { if (project.isOpen()) { int sev = project.findMaxProblemSeverity(null, true, IResource.DEPTH_INFINITE); int projectSev; switch (sev) { case IMarker.SEVERITY_ERROR: projectSev = IMessageProvider.ERROR; break; case IMarker.SEVERITY_INFO: projectSev = IMessageProvider.INFORMATION; break; case IMarker.SEVERITY_WARNING: projectSev = IMessageProvider.WARNING; break; default: projectSev = IMessageProvider.NONE; break; } projectSeverity.put(project, new Integer(projectSev)); } } } catch (CoreException e) { StudioLogger.error(PackageExportWizardArea.class, "Impossible to get project severity"); //$NON-NLS-1$ } } /** * Can finish used in {@link IWizardPage} This method validate the page and * change the severity/message. * * @return true if can finish this wizard, false otherwise */ public boolean canFinish() { String messageAux = null; int severity_aux = IMessageProvider.NONE; /* * Check is has selected items */ if (!hasItemChecked()) { messageAux = Messages.SELECTOR_MESSAGE_NO_SELECTION; if (treeSelectionChanged) { severity_aux = IMessageProvider.ERROR; } else { severity_aux = IMessageProvider.INFORMATION; } } // validate if some selected project has errors if (messageAux == null) { Iterator<IProject> iterator = getSelectedProjects().iterator(); while (iterator.hasNext() && (severity_aux != IMessageProvider.ERROR)) { severity_aux = projectSeverity.get(iterator.next()); } if (severity_aux == IMessageProvider.ERROR) { messageAux = Messages.PACKAGE_EXPORT_WIZARD_AREA_PROJECTS_WITH_ERRORS_SELECTED; } } /* * Check if the selected location is valid, even if non existent. */ IPath path = new Path(this.destinationText.getText()); if (!this.defaultDestination.getSelection() && (messageAux == null)) { // Test if path is blank, to warn user instead of show an error // message if (this.destinationText.getText().equals("")) //$NON-NLS-1$ { messageAux = Messages.SELECTOR_MESSAGE_LOCATION_ERROR_INVALID; severity_aux = IMessageProvider.INFORMATION; } /* * Do Win32 Validation */ if ((messageAux == null) && Platform.getOS().equalsIgnoreCase(Platform.OS_WIN32)) { // test path size if (path.toString().length() > 255) { messageAux = Messages.SELECTOR_MESSAGE_LOCATION_ERROR_PATH_TOO_LONG; severity_aux = IMessageProvider.ERROR; } String device = path.getDevice(); File deviceFile = null; if (device != null) { deviceFile = new File(path.getDevice()); } if ((device != null) && !deviceFile.exists()) { messageAux = Messages.SELECTOR_MESSAGE_LOCATION_ERROR_INVALID_DEVICE + " [" + device //$NON-NLS-1$ + "]"; //$NON-NLS-1$ severity_aux = IMessageProvider.ERROR; } } // test if path is absolute if (messageAux == null) { if (!path.isAbsolute()) { messageAux = Messages.SELECTOR_MESSAGE_LOCATION_ERROR_INVALID; severity_aux = IMessageProvider.ERROR; } } if (messageAux == null) { for (String folderName : path.segments()) { if (!ResourcesPlugin.getWorkspace().validateName(folderName, IResource.FOLDER) .isOK()) { messageAux = Messages.SELECTOR_MESSAGE_LOCATION_ERROR_INVALID; severity_aux = IMessageProvider.ERROR; } } } if ((messageAux == null) && path.toFile().exists() && !path.toFile().isDirectory()) { messageAux = Messages.SELECTOR_MESSAGE_LOCATION_ERROR_NOT_DIRECTORY; severity_aux = IMessageProvider.ERROR; } } /* * Check if there are available certificates and if selection isn't null */ if (messageAux == null) { if (this.signingEnabled && (this.signCheckBox != null) && this.signCheckBox.getSelection() && !((this.keystores != null) && (this.keystores.getItemCount() > 0))) { messageAux = Messages.PACKAGE_EXPORT_WIZARD_AREA_SIGN_NO_KEYSTORE_AVAILABLE; severity_aux = IMessageProvider.ERROR; } else if (this.signCheckBox.getSelection() && !((this.keysCombo != null) && (this.keysCombo.getItemCount() > 0) && (this.keysCombo.getSelectionIndex() >= 0) && (this.keysCombo.getItem(this.keysCombo.getSelectionIndex()) != null) && !this.keysCombo .getItem(this.keysCombo.getSelectionIndex()).equals(""))) //$NON-NLS-1$ { messageAux = Messages.PACKAGE_EXPORT_WIZARD_AREA_SIGN_NO_KEYSTORE_OR_KEY_SELECTED; severity_aux = IMessageProvider.ERROR; } } if (messageAux == null) { if (!this.signCheckBox.getSelection()) { messageAux = Messages.PACKAGE_EXPORT_WIZARD_AREA_UNSIGNEDPACKAGE_WARNING; severity_aux = IMessageProvider.WARNING; } } /* * Setting message */ if (messageAux == null) { messageAux = Messages.PACKAGE_EXPORT_WIZARD_AREA_DESCRIPTION; severity_aux = IMessageProvider.NONE; } this.message = messageAux; this.severity = severity_aux; boolean result; switch (severity_aux) { case IMessageProvider.ERROR: // ERROR. can't finish wizard result = false; break; case IMessageProvider.WARNING: // WARNING. ok to finish the wizard result = true; break; case IMessageProvider.INFORMATION: // INFORMATION. Path is empty, so it's NOT OK to finish the wizard result = false; break; default: // by default, canFinish returns true result = true; break; } return result; } /** * Check if we have at least one item checked * * @param items * @return true if some of tree item is checked, false otherwise */ private boolean hasItemChecked() { boolean checked = false; TreeItem[] items = tree.getItems(); int i = 0; while (!checked && (i < items.length)) { if (items[i].getChecked()) { checked = true; } i++; } return checked; } /** * Check if the destination folder is valid during finish action If the * package don't exist, ask to create * * @return true if the destination is valid * @throws CoreException * when errors with directory creation occurs */ private boolean checkDestination() throws CoreException { boolean destinationOK = true; if (!defaultDestination.getSelection()) { File destination = new File(destinationText.getText()); if (!destination.exists()) { destinationOK = createDestinationFolderDialog(); if (destinationOK) { if (!destination.mkdirs()) { throw new CoreException(new Status(IStatus.ERROR, PackagingUIPlugin.PLUGIN_ID, Messages.PACKAGE_EXPORT_WIZARD_AREA_ERROR_DESTINATION_CHECK)); } } } } else { for (TreeItem item : getSelectedItems()) { IProject project = (IProject) item.getData(); IPath dist = project.getLocation().append( CertificateManagerActivator.PACKAGE_PROJECT_DESTINATION); File file = dist.toFile(); if (file.exists() && !file.isDirectory()) { if (!file.delete()) { throw new CoreException(new Status(IStatus.ERROR, PackagingUIPlugin.PLUGIN_ID, Messages.PACKAGE_EXPORT_WIZARD_AREA_ERROR_DESTINATION_CHECK)); } } if (!file.exists()) { if (!file.mkdir()) { throw new CoreException(new Status(IStatus.ERROR, PackagingUIPlugin.PLUGIN_ID, Messages.PACKAGE_EXPORT_WIZARD_AREA_ERROR_DESTINATION_CHECK)); } } project.refreshLocal(IResource.DEPTH_ONE, new NullProgressMonitor()); } } return destinationOK; } /** * Create a destination folder, asking user's permission * * @return */ private boolean createDestinationFolderDialog() { MessageBox box = new MessageBox(parentComposite.getShell(), SWT.ICON_QUESTION | SWT.YES | SWT.NO); box.setMessage(Messages.PACKAGE_EXPORT_WIZARD_AREA_CREATE_DIRECTORIES_BOX_MESSAGE); box.setText(Messages.PACKAGE_EXPORT_WIZARD_AREA_CREATE_DIRECTORIES_BOX_TITLE); return box.open() == SWT.YES; } /** * do the finish: Create the package for each selected descriptor */ public boolean performFinish() { final boolean[] finished = { false }; boolean destOK = false; final MultiStatus status = new MultiStatus(PackagingUIPlugin.PLUGIN_ID, IStatus.OK, "", null); //$NON-NLS-1$ ProgressMonitorDialog monitorDialog = null; String DESCRIPTION_TO_LOG = StudioLogger.DESCRIPTION_DEFAULT; try { destOK = checkDestination(); } catch (CoreException e) { status.add(e.getStatus()); } if (destOK) { monitorDialog = new ProgressMonitorDialog(parentComposite.getShell()); try { monitorDialog.run(false, false, new IRunnableWithProgress() { @Override public void run(IProgressMonitor aMonitor) throws InvocationTargetException, InterruptedException { int finishSize = getSelectedItems().size() * PackagingUIPlugin.PROGRESS_MONITOR_MULTIPLIER; SubMonitor monitor = SubMonitor.convert(aMonitor); monitor.beginTask(Messages.PACKAGE_EXPORT_WIZARD_AREA_FINISH_ACTION_LABEL, finishSize); IPath exportDestinationFolder = new Path(destinationText.getText()); IPath exportDestinationFile = null; for (TreeItem item : getSelectedItems()) { // get the eclipse project as a java project to get // the project // destination IProject eclipseProject = (IProject) item.getData(); try { monitor.worked(PackagingUIPlugin.PROGRESS_MONITOR_MULTIPLIER / 4); JavaProject javaProject = new JavaProject(); javaProject.setProject(eclipseProject); // find all packages built by Android builder Map<String, String> apkConfigurations = SdkUtils.getAPKConfigurationsForProject(eclipseProject); Set<String> apkConfNames = new HashSet<String>(); if (apkConfigurations != null) { apkConfNames.addAll(apkConfigurations.keySet()); } apkConfNames.add(""); // the default package //$NON-NLS-1$ SubMonitor submonitor = monitor.newChild(PackagingUIPlugin.PROGRESS_MONITOR_MULTIPLIER / 4); submonitor.beginTask( Messages.PACKAGE_EXPORT_WIZARD_AREA_EXPORTING_ACTION_LABEL, 3 * PackagingUIPlugin.PROGRESS_MONITOR_MULTIPLIER * apkConfNames.size()); for (String apkConfName : apkConfNames) { String apkName = eclipseProject.getName() + (apkConfName.isEmpty() ? apkConfName : "-" //$NON-NLS-1$ //$NON-NLS-2$ + apkConfName); if (defaultDestination.getSelection()) { exportDestinationFolder = eclipseProject .getLocation() .append(CertificateManagerActivator.PACKAGE_PROJECT_DESTINATION); } exportDestinationFile = exportDestinationFolder .append(apkName) .addFileExtension( CertificateManagerActivator.PACKAGE_EXTENSION); File file = exportDestinationFile.toFile(); submonitor .worked(PackagingUIPlugin.PROGRESS_MONITOR_MULTIPLIER); //always export unsigned package AdtUtils.exportUnsignedReleaseApk(javaProject.getProject(), file, submonitor); submonitor .worked(PackagingUIPlugin.PROGRESS_MONITOR_MULTIPLIER); if (signCheckBox.getSelection()) { //sign the package if required IStatus signStatus = signPackage(eclipseProject, file); status.add(signStatus); } //zipalign the file and we are done exporting the package PackageFile.zipAlign(file); submonitor .worked(PackagingUIPlugin.PROGRESS_MONITOR_MULTIPLIER); } submonitor.done(); } catch (CoreException e) { StudioLogger.error(PackageExportWizardArea.class, "Error while building project or getting project output folder" //$NON-NLS-1$ + eclipseProject.getName(), e); status.add(new Status(IStatus.ERROR, PackagingUIPlugin.PLUGIN_ID, Messages.PACKAGE_EXPORT_WIZARD_AREA_ERROR_PROJECT_BUILD + " " + eclipseProject.getName())); //$NON-NLS-1$ } finally { try { eclipseProject.refreshLocal( IResource.DEPTH_INFINITE, monitor.newChild(PackagingUIPlugin.PROGRESS_MONITOR_MULTIPLIER / 4)); } catch (CoreException e) { // do nothing } } monitor.worked(PackagingUIPlugin.PROGRESS_MONITOR_MULTIPLIER / 4); } finished[0] = true; } }); } catch (Exception e) { StudioLogger.warn("Error finishing package export."); } } if (!status.isOK()) { status.getMessage(); DESCRIPTION_TO_LOG = Messages.PACKAGE_EXPORT_WIZARD_AREA_READONLY_TITLE; ErrorDialog.openError(parentComposite.getShell(), Messages.PACKAGE_EXPORT_WIZARD_AREA_READONLY_TITLE, Messages.PACKAGE_EXPORT_WIZARD_AREA_READONLY_MESSAGE, status); } // Saving usage data try { StudioLogger.collectUsageData(StudioLogger.WHAT_APP_MANAGEMENT_PACKAGE, StudioLogger.KIND_APP_MANAGEMENT, DESCRIPTION_TO_LOG, PackagingUIPlugin.PLUGIN_ID, PackagingUIPlugin.getDefault().getBundle() .getVersion().toString()); } catch (Throwable e) { // Do nothing, but error on the log should never prevent app from // working } return finished[0]; } /** * * @return key entry password */ public String getKeyEntryPassword() { String keyEntryPassword = new String(); try { keyEntryPassword = getSelectedKeyStore().getPasswordProvider().getPassword( this.keysCombo.getItem(this.keysCombo.getSelectionIndex()), true); } catch (KeyStoreManagerException e) { StudioLogger.error(this.getClass(), "Error retrieving keys entry password", e); //$NON-NLS-1$ } return keyEntryPassword; } /** * @param eclipseProject The project being exported. * @param exportedPackage The package to be signed * @throws InvalidPasswordException * @throws KeyStoreManagerException */ private IStatus signPackage(IProject eclipseProject, File exportedPackage) { IStatus status = Status.OK_STATUS; String keyAlias = keysCombo.getItem(keysCombo.getSelectionIndex()); String keystorePassword = getKeyStorePassword(getSelectedKeyStore()); String keyPassword = getKeyEntryPassword(); JarFile jar = null; try { PackageFile pack = null; boolean keepTrying; if (keyPassword != null) { keepTrying = true; } else { keepTrying = false; } while (keepTrying) { try { // Open package and remove signature jar = new JarFile(exportedPackage); pack = new PackageFile(jar); pack.removeMetaEntryFiles(); // Sign the new package PackageFileSigner.signPackage(pack, getSelectedKeyStore().getEntry(keyAlias, keystorePassword), keyPassword, PackageFileSigner.MOTODEV_STUDIO); keepTrying = false; } catch (UnrecoverableKeyException sE) { try { keyPassword = getSelectedKeyStore().getPasswordProvider().getPassword(keyAlias, true, false); } catch (KeyStoreManagerException e) { status = new Status(Status.ERROR, CertificateManagerActivator.PLUGIN_ID, e.getMessage()); StudioLogger.error(PackageExportWizardArea.this.getClass(), "Could not retrieve key password on export: " + e.getMessage()); //$NON-NLS-1$ } if (keyPassword == null) { keepTrying = false; status = Status.CANCEL_STATUS; } else { keepTrying = true; } } catch (InvalidPasswordException e) { // Should never happen as the entry alias is only available if the keystore password // was typed correctly. // Unless the user changed the keystore password outside the tool while exporting the package. status = new Status(Status.ERROR, CertificateManagerActivator.PLUGIN_ID, e.getMessage()); } catch (KeyStoreManagerException e) { // Should never happen as the entry alias is only available if the keystore password // was typed correctly. // Unless the user changed the keystore password outside the tool while exporting the package. status = new Status(Status.ERROR, CertificateManagerActivator.PLUGIN_ID, e.getMessage()); } } if (status.isOK()) { FileOutputStream fileToWrite = null; try { // Write the new package file fileToWrite = new FileOutputStream(exportedPackage); pack.write(fileToWrite); } finally { fileToWrite.close(); } } else { EclipseUtils.showErrorDialog("Package Signing", "Could not sign the package."); } } catch (IOException e) { StudioLogger.error(PackageExportWizardArea.this.getClass(), "Could not sign the package: " + e.getMessage()); //$NON-NLS-1$ status = new Status(IStatus.ERROR, PackagingUIPlugin.PLUGIN_ID, Messages.PackageExportWizardArea_ErrorWritingSignedPackageFile + " " //$NON-NLS-1$ + eclipseProject.getName()); } catch (SignException e) { StudioLogger.error(PackageExportWizardArea.this.getClass(), "Could not sign the package: " + e.getMessage()); //$NON-NLS-1$ status = new Status(IStatus.ERROR, PackagingUIPlugin.PLUGIN_ID, Messages.PackageExportWizardArea_ErrorSigningPackage + " " + eclipseProject.getName()); //$NON-NLS-1$ } finally { try { jar.close(); } catch (IOException e) { StudioLogger.error(PackageExportWizardArea.this.getClass(), "Could not sign the package: " + e.getMessage()); //$NON-NLS-1$ } } return status; } /** * Get the area message * * @return the message for the wizard */ public String getMessage() { return this.message; } /** * Get the area error severity * * @return */ public int getSeverity() { return this.severity; } /** * "Browser..." button for package destination. * */ private class PackageDestinationButtonListener implements Listener { /* * (non-Javadoc) * * @see * org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets * .Event) */ @Override public void handleEvent(Event event) { String path = destinationText.getText(); if (path.equals("")) //$NON-NLS-1$ { path = ResourcesPlugin.getWorkspace().getRoot().getLocation().toOSString() .subSequence(0, 2).toString(); } String executablePath = directoryDialog(path); if ((executablePath != null) && !executablePath.equals("")) //$NON-NLS-1$ { destinationText.setText(executablePath); } parentComposite.notifyListeners(SWT.Modify, new Event()); } } /** * Update the default destination status * */ private class DestinationTextListener implements Listener { @Override public void handleEvent(Event event) { if (!defaultDestination.getSelection()) { parentComposite.notifyListeners(SWT.Modify, new Event()); } } } /** * A Listener for the Tree. * */ private class TreeListener implements Listener { @Override public void handleEvent(Event event) { updateDefaultDestination(); parentComposite.notifyListeners(SWT.Modify, new Event()); treeSelectionChanged = true; } } /** * Enable Destination Text. * */ private class DefaultDestinationListener implements Listener { /* * (non-Javadoc) * * @see * org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets * .Event) */ @Override public void handleEvent(Event event) { Button defaultDestination = (Button) event.widget; destinationText.setEnabled(!defaultDestination.getSelection()); packageDestinationBrowseButton.setEnabled(!defaultDestination.getSelection()); if (defaultDestination.getSelection()) { updateDefaultDestination(); } else { destinationText.setText(""); //$NON-NLS-1$ } destinationText.notifyListeners(SWT.Modify, new Event()); parentComposite.notifyListeners(SWT.Modify, new Event()); } } /** * * @param notify Notifies listener that validates if Finish can be enabled */ private void setEnablement(boolean notify) { boolean signEnabled = signCheckBox.getSelection(); keysCombo.setEnabled(signEnabled); keystores.setEnabled(signEnabled); buttonAddKey.setEnabled(signEnabled); //toolbar.setEnabled(signEnabled); buttonExisting.setEnabled(signEnabled); buttonAddNew.setEnabled(signEnabled); if (signEnabled) { if (keystores.getSelectionIndex() < 0) { //no keystore selected, clear keys combo buttonAddKey.setEnabled(false); keysCombo.setEnabled(false); keysCombo.removeAll(); } if (keystores.getItemCount() <= 0) { keysCombo.setEnabled(false); keystores.setEnabled(false); buttonAddKey.setEnabled(false); //toolbar.setEnabled(true); buttonExisting.setEnabled(signEnabled); buttonAddNew.setEnabled(signEnabled); } } if (notify) { parentComposite.notifyListeners(SWT.Modify, new Event()); } } protected void openSelectKeystoreWizard() { SelectExistentKeystoreWizard selectExistentKeystoreWizard = new SelectExistentKeystoreWizard(); WizardDialog dialog = new WizardDialog(parentComposite.getShell(), selectExistentKeystoreWizard); //open the wizard to select keystores dialog.create(); if (dialog.open() == Window.OK) { //keystore was really selected : adding keystore to the list IKeyStore iKeyStore = selectExistentKeystoreWizard.getSelectedKeystore(); boolean canSavePassword = selectExistentKeystoreWizard.canSavePassword(); String password = selectExistentKeystoreWizard.getPassword(); addKeystore(iKeyStore, canSavePassword, password); selectKeystore(iKeyStore); } } /** * This method must be used to retrieve the passwod of any keystore in the context of this wizard. * It is purpose is to cache the passwords of the keystores, mainly of the ones that do not have its password saved, * so the password will be asked only once for each keystore, during the lifetime of this wizard. * */ protected String getKeyStorePassword(IKeyStore keystore) { String password = keystorePasswords.get(keystore); //check if password is already cached if (password == null) { password = keystore.getKeyStorePassword(true); } if (password != null) { keystorePasswords.put(keystore, password); } return password; } /** * Sign button listener to enable/disable combos, buttons and finish/next * */ private class SignButtonListener implements Listener { /* * (non-Javadoc) * * @see * org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets * .Event) */ @Override public void handleEvent(Event event) { if (event.widget == signCheckBox) { setEnablement(true); } } } /** * This class will handle the (Des)Select All buttons * */ private class TreeSelectionButtonListener implements Listener { private final boolean checked; /** * Create a new instance of the listener with the desired check state * * @param selectItems */ public TreeSelectionButtonListener(boolean selectItems) { this.checked = selectItems; } /* * (non-Javadoc) * * @see * org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets * .Event) */ @Override public void handleEvent(Event event) { for (TreeItem item : tree.getItems()) { item.setChecked(checked); } updateDefaultDestination(); parentComposite.notifyListeners(SWT.Modify, new Event()); treeSelectionChanged = true; } } }