| /* |
| * 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; |
| } |
| |
| } |
| } |