FileDocCategorySizeDatePackage
KeyCheckPage.javaAPI DocAndroid 1.5 API18758Wed May 06 22:41:10 BST 2009com.android.ide.eclipse.adt.project.export

KeyCheckPage

public final class KeyCheckPage extends com.android.ide.eclipse.adt.project.export.ExportWizard.ExportWizardPage
Final page of the wizard that checks the key and ask for the ouput location.

Fields Summary
private final ExportWizard
mWizard
private PrivateKey
mPrivateKey
private X509Certificate
mCertificate
private org.eclipse.swt.widgets.Text
mDestination
private boolean
mFatalSigningError
private org.eclipse.ui.forms.widgets.FormText
mDetailText
private Map
mApkConfig
The Apk Config map for the current project
private org.eclipse.swt.custom.ScrolledComposite
mScrolledComposite
private String
mKeyDetails
private String
mDestinationDetails
Constructors Summary
protected KeyCheckPage(ExportWizard wizard, String pageName)

        super(pageName);
        mWizard = wizard;
        
        setTitle("Destination and key/certificate checks");
        setDescription(""); // TODO
    
Methods Summary
public voidcreateControl(org.eclipse.swt.widgets.Composite parent)

        setErrorMessage(null);
        setMessage(null);

        // build the ui.
        Composite composite = new Composite(parent, SWT.NULL);
        composite.setLayoutData(new GridData(GridData.FILL_BOTH));
        GridLayout gl = new GridLayout(3, false);
        gl.verticalSpacing *= 3;
        composite.setLayout(gl);
        
        GridData gd;

        new Label(composite, SWT.NONE).setText("Destination APK file:");
        mDestination = new Text(composite, SWT.BORDER);
        mDestination.setLayoutData(gd = new GridData(GridData.FILL_HORIZONTAL));
        mDestination.addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent e) {
                onDestinationChange(false /*forceDetailUpdate*/);
            }
        });
        final Button browseButton = new Button(composite, SWT.PUSH);
        browseButton.setText("Browse...");
        browseButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                FileDialog fileDialog = new FileDialog(browseButton.getShell(), SWT.SAVE);
                
                fileDialog.setText("Destination file name");
                // get a default apk name based on the project
                String filename = ProjectHelper.getApkFilename(mWizard.getProject(),
                        null /*config*/);
                fileDialog.setFileName(filename);
        
                String saveLocation = fileDialog.open();
                if (saveLocation != null) {
                    mDestination.setText(saveLocation);
                }
            }
        });
        
        mScrolledComposite = new ScrolledComposite(composite, SWT.V_SCROLL);
        mScrolledComposite.setLayoutData(gd = new GridData(GridData.FILL_BOTH));
        gd.horizontalSpan = 3;
        mScrolledComposite.setExpandHorizontal(true);
        mScrolledComposite.setExpandVertical(true);
        
        mDetailText = new FormText(mScrolledComposite, SWT.NONE);
        mScrolledComposite.setContent(mDetailText);

        mScrolledComposite.addControlListener(new ControlAdapter() {
            @Override
            public void controlResized(ControlEvent e) {
                updateScrolling();
            }
        });
        
        setControl(composite);
    
private java.util.MapgetApkFileMap(java.io.File file)
Creates the list of destination filenames based on the content of the destination field and the list of APK configurations for the project.

param
file File name from the destination field
return
A list of destination filenames based file and the list of APK configurations for the project.

        String filename = file.getName();
        
        HashMap<String, String[]> map = new HashMap<String, String[]>();
        
        // add the default APK filename
        String[] apkArray = new String[ExportWizard.APK_COUNT];
        apkArray[ExportWizard.APK_FILE_SOURCE] = ProjectHelper.getApkFilename(
                mWizard.getProject(), null /*config*/);
        apkArray[ExportWizard.APK_FILE_DEST] = filename;
        map.put(null, apkArray);

        // add the APKs for each APK configuration.
        if (mApkConfig != null && mApkConfig.size() > 0) {
            // remove the extension.
            int index = filename.lastIndexOf('.");
            String base = filename.substring(0, index);
            String extension = filename.substring(index);
            
            Set<Entry<String, String>> set = mApkConfig.entrySet();
            for (Entry<String, String> entry : set) {
                apkArray = new String[ExportWizard.APK_COUNT];
                apkArray[ExportWizard.APK_FILE_SOURCE] = ProjectHelper.getApkFilename(
                        mWizard.getProject(), entry.getKey());
                apkArray[ExportWizard.APK_FILE_DEST] = base + "-" + entry.getKey() + extension;
                map.put(entry.getKey(), apkArray);
            }
        }
        
        return map;
    
private voidonDestinationChange(boolean forceDetailUpdate)
Callback for destination field edition

param
forceDetailUpdate if true, the detail {@link FormText} is updated even if a fatal error has happened in the signing.

        if (mFatalSigningError == false) {
            // reset messages for now.
            setErrorMessage(null);
            setMessage(null);

            String path = mDestination.getText().trim();

            if (path.length() == 0) {
                setErrorMessage("Enter destination for the APK file.");
                // reset canFinish in the wizard.
                mWizard.resetDestination();
                setPageComplete(false);
                return;
            }

            File file = new File(path);
            if (file.isDirectory()) {
                setErrorMessage("Destination is a directory.");
                // reset canFinish in the wizard.
                mWizard.resetDestination();
                setPageComplete(false);
                return;
            }

            File parentFolder = file.getParentFile();
            if (parentFolder == null || parentFolder.isDirectory() == false) {
                setErrorMessage("Not a valid directory.");
                // reset canFinish in the wizard.
                mWizard.resetDestination();
                setPageComplete(false);
                return;
            }

            // display the list of files that will actually be created
            Map<String, String[]> apkFileMap = getApkFileMap(file);
            
            // display them
            boolean fileExists = false;
            StringBuilder sb = new StringBuilder(String.format(
                    "<p>This will create the following files:</p>"));
            
            Set<Entry<String, String[]>> set = apkFileMap.entrySet();
            for (Entry<String, String[]> entry : set) {
                String[] apkArray = entry.getValue();
                String filename = apkArray[ExportWizard.APK_FILE_DEST];
                File f = new File(parentFolder, filename);
                if (f.isFile()) {
                    fileExists = true;
                    sb.append(String.format("<li>%1$s (WARNING: already exists)</li>", filename));
                } else if (f.isDirectory()) {
                    setErrorMessage(String.format("%1$s is a directory.", filename));
                    // reset canFinish in the wizard.
                    mWizard.resetDestination();
                    setPageComplete(false);
                    return;
                } else {
                    sb.append(String.format("<li>%1$s</li>", filename));
                }
            }

            mDestinationDetails = sb.toString();

            // no error, set the destination in the wizard.
            mWizard.setDestination(parentFolder, apkFileMap);
            setPageComplete(true);

            // However, we should also test if the file already exists.
            if (fileExists) {
                setMessage("A destination file already exists.", WARNING);
            }

            updateDetailText();
        } else if (forceDetailUpdate) {
            updateDetailText();
        }
    
protected voidonException(java.lang.Throwable t)

        super.onException(t);
        
        mKeyDetails = String.format("ERROR: %1$s", ExportWizard.getExceptionMessage(t));
    
voidonShow()

        // fill the texts with information loaded from the project.
        if ((mProjectDataChanged & DATA_PROJECT) != 0) {
            // reset the destination from the content of the project
            IProject project = mWizard.getProject();
            mApkConfig = Sdk.getCurrent().getProjectApkConfigs(project);
            
            String destination = ProjectHelper.loadStringProperty(project,
                    ExportWizard.PROPERTY_DESTINATION);
            String filename = ProjectHelper.loadStringProperty(project,
                    ExportWizard.PROPERTY_FILENAME);
            if (destination != null && filename != null) {
                mDestination.setText(destination + File.separator + filename);
            }
        }
        
        // if anything change we basically reload the data.
        if (mProjectDataChanged != 0) {
            mFatalSigningError = false;

            // reset the wizard with no key/cert to make it not finishable, unless a valid
            // key/cert is found.
            mWizard.setSigningInfo(null, null);
            mPrivateKey = null;
            mCertificate = null;
            mKeyDetails = null;
    
            if (mWizard.getKeystoreCreationMode() || mWizard.getKeyCreationMode()) {
                int validity = mWizard.getValidity();
                StringBuilder sb = new StringBuilder(
                        String.format("<p>Certificate expires in %d years.</p>",
                        validity));

                if (validity < 25) {
                    sb.append("<p>Make sure the certificate is valid for the planned lifetime of the product.</p>");
                    sb.append("<p>If the certificate expires, you will be forced to sign your application with a different one.</p>");
                    sb.append("<p>Applications cannot be upgraded if their certificate changes from one version to another, ");
                    sb.append("forcing a full uninstall/install, which will make the user lose his/her data.</p>");
                    sb.append("<p>Android Market currently requires certificates to be valid until 2033.</p>");
                }

                mKeyDetails = sb.toString();
            } else {
                try {
                    KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
                    FileInputStream fis = new FileInputStream(mWizard.getKeystore());
                    keyStore.load(fis, mWizard.getKeystorePassword().toCharArray());
                    fis.close();
                    PrivateKeyEntry entry = (KeyStore.PrivateKeyEntry)keyStore.getEntry(
                            mWizard.getKeyAlias(),
                            new KeyStore.PasswordProtection(
                                    mWizard.getKeyPassword().toCharArray()));
                    
                    if (entry != null) {
                        mPrivateKey = entry.getPrivateKey();
                        mCertificate = (X509Certificate)entry.getCertificate();
                    } else {
                        setErrorMessage("Unable to find key.");
                        
                        setPageComplete(false);
                    }
                } catch (FileNotFoundException e) {
                    // this was checked at the first previous step and will not happen here, unless
                    // the file was removed during the export wizard execution.
                    onException(e);
                } catch (KeyStoreException e) {
                    onException(e);
                } catch (NoSuchAlgorithmException e) {
                    onException(e);
                } catch (UnrecoverableEntryException e) {
                    onException(e);
                } catch (CertificateException e) {
                    onException(e);
                } catch (IOException e) {
                    onException(e);
                }
                
                if (mPrivateKey != null && mCertificate != null) {
                    Calendar expirationCalendar = Calendar.getInstance();
                    expirationCalendar.setTime(mCertificate.getNotAfter());
                    Calendar today = Calendar.getInstance();
                    
                    if (expirationCalendar.before(today)) {
                        mKeyDetails = String.format(
                                "<p>Certificate expired on %s</p>",
                                mCertificate.getNotAfter().toString());
                        
                        // fatal error = nothing can make the page complete.
                        mFatalSigningError = true;
        
                        setErrorMessage("Certificate is expired.");
                        setPageComplete(false);
                    } else {
                        // valid, key/cert: put it in the wizard so that it can be finished
                        mWizard.setSigningInfo(mPrivateKey, mCertificate);
        
                        StringBuilder sb = new StringBuilder(String.format(
                                "<p>Certificate expires on %s.</p>",
                                mCertificate.getNotAfter().toString()));
                        
                        int expirationYear = expirationCalendar.get(Calendar.YEAR);
                        int thisYear = today.get(Calendar.YEAR);
                        
                        if (thisYear + 25 < expirationYear) {
                            // do nothing
                        } else {
                            if (expirationYear == thisYear) {
                                sb.append("<p>The certificate expires this year.</p>");
                            } else {
                                int count = expirationYear-thisYear;
                                sb.append(String.format(
                                        "<p>The Certificate expires in %1$s %2$s.</p>",
                                        count, count == 1 ? "year" : "years"));
                            }
                            
                            sb.append("<p>Make sure the certificate is valid for the planned lifetime of the product.</p>");
                            sb.append("<p>If the certificate expires, you will be forced to sign your application with a different one.</p>");
                            sb.append("<p>Applications cannot be upgraded if their certificate changes from one version to another, ");
                            sb.append("forcing a full uninstall/install, which will make the user lose his/her data.</p>");
                            sb.append("<p>Android Market currently requires certificates to be valid until 2033.</p>");
                        }
                        
                        mKeyDetails = sb.toString();
                    }
                } else {
                    // fatal error = nothing can make the page complete.
                    mFatalSigningError = true;
                }
            }
        }

        onDestinationChange(true /*forceDetailUpdate*/);
    
private voidupdateDetailText()

        StringBuilder sb = new StringBuilder("<form>");
        if (mKeyDetails != null) {
            sb.append(mKeyDetails);
        }
        
        if (mDestinationDetails != null && mFatalSigningError == false) {
            sb.append(mDestinationDetails);
        }
        
        sb.append("</form>");
        
        mDetailText.setText(sb.toString(), true /* parseTags */,
                true /* expandURLs */);

        mDetailText.getParent().layout();

        updateScrolling();

    
private voidupdateScrolling()
Updates the scrollbar to match the content of the {@link FormText} or the new size of the {@link ScrolledComposite}.

        if (mDetailText != null) {
            Rectangle r = mScrolledComposite.getClientArea();
            mScrolledComposite.setMinSize(mDetailText.computeSize(r.width, SWT.DEFAULT));
            mScrolledComposite.layout();
        }